From 9098391fe334d829ec4101f190b8f1fa21c30752 Mon Sep 17 00:00:00 2001 From: Tim King Date: Fri, 26 Apr 2013 17:10:21 -0400 Subject: [PATCH] FCSimplex branch merge --- COPYING | 23 +- INSTALL | 9 + Makefile | 6 +- config/glpk.m4 | 118 + configure.ac | 14 +- src/Makefile.am | 5 + src/context/cdhashset.h | 2 +- src/context/cdlist.h | 1 + src/main/options_handlers.h | 1 + src/theory/arith/Makefile.am | 28 +- src/theory/arith/approx_simplex.cpp | 583 ++++ src/theory/arith/approx_simplex.h | 90 + .../arith/arith_heuristic_pivot_rule.cpp | 14 +- src/theory/arith/arith_heuristic_pivot_rule.h | 11 +- src/theory/arith/arith_priority_queue.cpp | 346 --- src/theory/arith/arith_priority_queue.h | 337 --- src/theory/arith/arith_static_learner.cpp | 6 + src/theory/arith/arith_utilities.h | 27 + src/theory/arith/arithvar.h | 52 +- src/theory/arith/bound_counts.h | 144 + src/theory/arith/callbacks.cpp | 37 + src/theory/arith/callbacks.h | 92 + src/theory/arith/congruence_manager.cpp | 8 +- src/theory/arith/congruence_manager.h | 25 +- src/theory/arith/constraint.cpp | 34 +- src/theory/arith/constraint.h | 25 +- src/theory/arith/delta_rational.h | 28 +- src/theory/arith/dio_solver.cpp | 73 +- src/theory/arith/dio_solver.h | 34 +- src/theory/arith/dual_simplex.cpp | 259 ++ src/theory/arith/dual_simplex.h | 115 + src/theory/arith/error_set.cpp | 493 +++ src/theory/arith/error_set.h | 407 +++ src/theory/arith/fc_simplex.cpp | 853 ++++++ src/theory/arith/fc_simplex.h | 252 ++ src/theory/arith/linear_equality.cpp | 1326 +++++++- src/theory/arith/linear_equality.h | 629 +++- src/theory/arith/matrix.cpp | 548 +--- src/theory/arith/matrix.h | 320 +- src/theory/arith/options | 27 +- src/theory/arith/options_handlers.h | 20 +- src/theory/arith/partial_model.cpp | 549 +++- src/theory/arith/partial_model.h | 277 +- src/theory/arith/pure_update_simplex.cpp | 261 ++ src/theory/arith/pure_update_simplex.h | 118 + src/theory/arith/simplex-converge.cpp | 1674 ++++++++++ src/theory/arith/simplex-converge.h | 531 ++++ src/theory/arith/simplex.cpp | 671 +--- src/theory/arith/simplex.h | 264 +- src/theory/arith/simplex_update.cpp | 192 ++ src/theory/arith/simplex_update.h | 352 +++ src/theory/arith/soi_simplex.cpp | 791 +++++ src/theory/arith/soi_simplex.h | 228 ++ src/theory/arith/tableau.cpp | 179 ++ src/theory/arith/tableau.h | 143 + src/theory/arith/tableau_sizes.cpp | 18 + src/theory/arith/tableau_sizes.h | 28 + src/theory/arith/theory_arith.cpp | 2530 +--------------- src/theory/arith/theory_arith.h | 525 +--- src/theory/arith/theory_arith_private.cpp | 2692 +++++++++++++++++ src/theory/arith/theory_arith_private.h | 605 ++++ .../arith/theory_arith_private_forward.h | 14 + src/theory/quantifiers/inst_strategy_cbqi.cpp | 30 +- src/theory/quantifiers/inst_strategy_cbqi.h | 4 +- src/util/Makefile.am | 1 + src/util/configuration.cpp | 4 + src/util/configuration.h | 2 + src/util/configuration_private.h | 17 +- src/util/dense_map.h | 30 +- src/util/maybe.h | 84 + src/util/rational_cln_imp.h | 27 +- src/util/rational_gmp_imp.h | 17 +- src/util/statistics_registry.h | 42 + .../regress0/arith/integers/Makefile.am | 10 +- test/regress/regress0/push-pop/Makefile.am | 4 +- 75 files changed, 14799 insertions(+), 5537 deletions(-) create mode 100644 config/glpk.m4 create mode 100644 src/theory/arith/approx_simplex.cpp create mode 100644 src/theory/arith/approx_simplex.h delete mode 100644 src/theory/arith/arith_priority_queue.cpp delete mode 100644 src/theory/arith/arith_priority_queue.h create mode 100644 src/theory/arith/bound_counts.h create mode 100644 src/theory/arith/callbacks.cpp create mode 100644 src/theory/arith/callbacks.h create mode 100644 src/theory/arith/dual_simplex.cpp create mode 100644 src/theory/arith/dual_simplex.h create mode 100644 src/theory/arith/error_set.cpp create mode 100644 src/theory/arith/error_set.h create mode 100644 src/theory/arith/fc_simplex.cpp create mode 100644 src/theory/arith/fc_simplex.h create mode 100644 src/theory/arith/pure_update_simplex.cpp create mode 100644 src/theory/arith/pure_update_simplex.h create mode 100644 src/theory/arith/simplex-converge.cpp create mode 100644 src/theory/arith/simplex-converge.h create mode 100644 src/theory/arith/simplex_update.cpp create mode 100644 src/theory/arith/simplex_update.h create mode 100644 src/theory/arith/soi_simplex.cpp create mode 100644 src/theory/arith/soi_simplex.h create mode 100644 src/theory/arith/tableau.cpp create mode 100644 src/theory/arith/tableau.h create mode 100644 src/theory/arith/tableau_sizes.cpp create mode 100644 src/theory/arith/tableau_sizes.h create mode 100644 src/theory/arith/theory_arith_private.cpp create mode 100644 src/theory/arith/theory_arith_private.h create mode 100644 src/theory/arith/theory_arith_private_forward.h create mode 100644 src/util/maybe.h diff --git a/COPYING b/COPYING index 38196791a..2294ebcbc 100644 --- a/COPYING +++ b/COPYING @@ -4,8 +4,8 @@ and The University of Iowa. All rights reserved. CVC4 is open-source; distribution is under the terms of the modified BSD license. However, certain builds of CVC4 link against GPLed libraries, and therefore the use of these builds is restricted in non-open-source -projects. See below for a discussion of CLN and how to ensure you have -a build that doesn't link against GPLed libraries. +projects. See below for a discussion of CLN and GLPK, and how to ensure +you have a build that doesn't link against GPLed libraries. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNERS AND CONTRIBUTORS ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -19,7 +19,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --- Morgan Deters Mon, 28 Jan 2013 17:22:36 -0500 +-- Morgan Deters Thu, 25 Apr 2013 15:45:40 -0400 CVC4 incorporates MiniSat code, excluded from the above copyright. See src/sat/minisat. Its copyright: @@ -237,10 +237,23 @@ for Numbers, available here: http://www.ginac.de/CLN/ -Please be advised that as this class library is covered under the GPLv3, if -you choose to use the combined work, "CVC4+CLN," by building CVC4 with CLN, +Please be advised that as this library is covered under the GPLv3, if you +choose to use the combined work, "CVC4+CLN," by building CVC4 with CLN, then it is also covered under the GPLv3. If you want to make sure you build a version of CVC4 that uses libgmp, the GNU Multiple Precision Arithmetic Library, configure CVC4 with "--with-gmp" before building (though that is the default). It can then be used in contexts where you want to license CVC4 under the (modified) BSD license. + +Certain builds of CVC4 link against a GPLed library, GLPK, the GNU Linear +Programming Kit, available here: + + http://www.gnu.org/software/glpk/ + +Please be advised that as this library is covered under the GPLv3, if +you choose to use the combined work, "CVC4+GLPK," by building CVC4 with +GLPK, then it is also covered under the GPLv3. If you want to make sure +you build a version of CVC4 that does not use GLPK, configure CVC4 with +"--without-glpk" before building (though that is the default). It can +then be used in contexts where you want to license CVC4 under the +(modified) BSD license. diff --git a/INSTALL b/INSTALL index ff8824ef7..57620bd72 100644 --- a/INSTALL +++ b/INSTALL @@ -96,6 +96,7 @@ None of these is required, but can improve CVC4 as described below: Optional: SWIG 2.0.x (Simplified Wrapper and Interface Generator) Optional: CLN v1.3 or newer (Class Library for Numbers) + Optional: GLPK (GNU Linear Programming Kit) Optional: GNU Readline library (for an improved interactive experience) Optional: The Boost C++ threading library (libboost_thread) Optional: CxxTest unit testing framework @@ -114,6 +115,14 @@ CVC4 with CLN support, you are licensing CVC4 under that same license. COPYING in the CVC4 source distribution for details.) Please visit http://www.ginac.de/CLN/ for more details about CLN. +GLPK is the GNU Linear Programming Kit, and can be used to speed up +the arithmetic implementation in CVC4. GLPK is covered by the GNU +General Public License, version 3; so if you choose to use CVC4 with +GLPK support, you are licensing CVC4 under that same license. +(Usually CVC4's license is more permissive; see above discussion.) +Please visit http://www.gnu.org/software/glpk/ for more details about +GLPK. + The GNU Readline library is optionally used to provide command editing, tab completion, and history functionality at the CVC prompt (when running in interactive mode). Check your distribution for a diff --git a/Makefile b/Makefile index ab9834ed2..468371197 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ submission submission-main: exit 1; \ fi ./autogen.sh - ./configure competition --disable-shared --enable-static-binary --with-cln + ./configure competition --disable-shared --enable-static-binary --with-cln --with-glpk $(MAKE) strip builds/bin/cvc4 $(MAKE) check @@ -67,7 +67,7 @@ submission-application: exit 1; \ fi ./autogen.sh - ./configure competition --disable-shared --enable-static-binary --with-cln CXXFLAGS=-DCVC4_SMTCOMP_APPLICATION_TRACK CFLAGS=-DCVC4_SMTCOMP_APPLICATION_TRACK + ./configure competition --disable-shared --enable-static-binary --with-cln --with-glpk CXXFLAGS=-DCVC4_SMTCOMP_APPLICATION_TRACK CFLAGS=-DCVC4_SMTCOMP_APPLICATION_TRACK $(MAKE) strip builds/bin/cvc4 $(MAKE) check @@ -88,7 +88,7 @@ submission-parallel: exit 1; \ fi ./autogen.sh - ./configure competition --disable-shared --enable-static-binary --with-gmp --with-portfolio + ./configure competition --disable-shared --enable-static-binary --with-gmp --with-portfolio --with-glpk $(MAKE) strip builds/bin/pcvc4 # some test cases fail (and are known to fail) diff --git a/config/glpk.m4 b/config/glpk.m4 new file mode 100644 index 000000000..7380ad0e2 --- /dev/null +++ b/config/glpk.m4 @@ -0,0 +1,118 @@ +# CVC4_CHECK_FOR_GLPK +# ------------------- +# Look for glpk and link it in, but only if user requested. +AC_DEFUN([CVC4_CHECK_FOR_GLPK], [ +AC_MSG_CHECKING([whether user requested glpk support]) +LIBGLPK= +have_libglpk=0 +GLPK_LIBS= +if test "$with_glpk" = no; then + AC_MSG_RESULT([no, glpk disabled by user]) +elif test "$with_glpk" = yes; then + AC_MSG_RESULT([yes, glpk requested by user]) + + dnl Try a bunch of combinations until something works :-/ + GLPK_LIBS= + AC_CHECK_HEADER([glpk.h], [], + [AC_MSG_FAILURE([cannot find glpk.h, the GLPK header!])]) + AC_MSG_CHECKING([how to link glpk]) + CVC4_TRY_GLPK_WITH([]) + CVC4_TRY_GLPK_WITH([-lgmp]) + CVC4_TRY_GLPK_WITH([-lz]) + CVC4_TRY_GLPK_WITH([-ldl]) + CVC4_TRY_GLPK_WITH([-lltdl]) + CVC4_TRY_GLPK_WITH([-lltdl -ldl]) + CVC4_TRY_GLPK_WITH([-lz -ldl]) + CVC4_TRY_GLPK_WITH([-lz -lltdl]) + CVC4_TRY_GLPK_WITH([-lz -lltdl -ldl]) + CVC4_TRY_GLPK_WITH([-lgmp -lz]) + CVC4_TRY_GLPK_WITH([-lgmp -ldl]) + CVC4_TRY_GLPK_WITH([-lgmp -lltdl]) + CVC4_TRY_GLPK_WITH([-lgmp -lltdl -ldl]) + CVC4_TRY_GLPK_WITH([-lgmp -lz -ldl]) + CVC4_TRY_GLPK_WITH([-lgmp -lz -lltdl]) + CVC4_TRY_GLPK_WITH([-lgmp -lz -lltdl -ldl]) + if test -z "$GLPK_LIBS"; then + AC_MSG_FAILURE([cannot link against libglpk! (or it's too old, or can't get it to work)]) + else + AC_MSG_RESULT([$GLPK_LIBS]) + # make sure it works in static builds, too + if test "$enable_static_binary" = yes; then + GLPK_LIBS= + AC_MSG_CHECKING([whether statically-linked glpk is functional]) + CVC4_TRY_STATIC_GLPK_WITH([]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp]) + CVC4_TRY_STATIC_GLPK_WITH([-lz]) + CVC4_TRY_STATIC_GLPK_WITH([-ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lltdl]) + CVC4_TRY_STATIC_GLPK_WITH([-lltdl -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lz -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lz -lltdl]) + CVC4_TRY_STATIC_GLPK_WITH([-lz -lltdl -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lz]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lltdl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lltdl -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lz -ldl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lz -lltdl]) + CVC4_TRY_STATIC_GLPK_WITH([-lgmp -lz -lltdl -ldl]) + if test -n "$GLPK_LIBS"; then + AC_MSG_RESULT([yes, it works]) + with_glpk=yes + else + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([glpk installation appears incompatible with static-binary]) + fi + else + with_glpk=yes + fi + fi + if test "$with_glpk" = yes; then + have_libglpk=1 + else + with_glpk=no + have_libreadline=0 + GLPK_LIBS= + fi +else + AC_MSG_RESULT([no, user didn't request glpk]) + with_glpk=no +fi +])# CVC4_CHECK_FOR_GLPK + +# CVC4_TRY_GLPK_WITH(LIBS) +# ------------------------ +# Try AC_CHECK_LIB(glpk) with the given linking libraries +AC_DEFUN([CVC4_TRY_GLPK_WITH], [ +if test -z "$GLPK_LIBS"; then + AC_LANG_PUSH([C++]) + cvc4_save_LIBS="$LIBS" + LIBS="-lglpk $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int i = lpx_get_int_parm(NULL, LPX_K_ITCNT)])], + [GLPK_LIBS="-lglpk $1"], + []) + LIBS="$cvc4_save_LIBS" + AC_LANG_POP([C++]) +fi +])# CVC4_TRY_GLPK_WITH + +# CVC4_TRY_STATIC_GLPK_WITH(LIBS) +# ------------------------------- +# Try AC_CHECK_LIB(glpk) with the given linking libraries +AC_DEFUN([CVC4_TRY_STATIC_GLPK_WITH], [ +if test -z "$GLPK_LIBS"; then + AC_LANG_PUSH([C++]) + cvc4_save_LIBS="$LIBS" + cvc4_save_LDFLAGS="$LDFLAGS" + LDFLAGS="-static $LDFLAGS" + LIBS="-lglpk $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int i = lpx_get_int_parm(NULL, LPX_K_ITCNT)])], + [GLPK_LIBS="-lglpk $1"], + []) + LIBS="$cvc4_save_LIBS" + LDFLAGS="$cvc4_save_LDFLAGS" + AC_LANG_POP([C++]) +fi +])# CVC4_TRY_STATIC_GLPK_WITH diff --git a/configure.ac b/configure.ac index 2a48f4b13..deee4fa68 100644 --- a/configure.ac +++ b/configure.ac @@ -496,7 +496,7 @@ if test "$enable_static_binary" = yes; then fi fi -AC_MSG_CHECKING([whether to support proof in libcvc4]) +AC_MSG_CHECKING([whether to support proofs in libcvc4]) AC_ARG_ENABLE([proof], [AS_HELP_STRING([--enable-proof], @@ -708,6 +708,17 @@ if test "$enable_profiling" = yes; then CVC4LDFLAGS="${CVC4LDFLAGS:+$CVC4LDFLAGS }-pg" fi +# Check for libglpk (defined in config/glpk.m4) +AC_ARG_WITH([glpk], + [AS_HELP_STRING([--with-glpk], + [use GLPK simplex solver])], [], [with_glpk=]) +CVC4_CHECK_FOR_GLPK +if test $have_libglpk -eq 1; then + CVC4CPPFLAGS="${CVC4CPPFLAGS:+$CVC4CPPFLAGS }-DCVC4_USE_GLPK" +fi +AM_CONDITIONAL([CVC4_USE_GLPK], [test $have_libglpk -eq 1]) +AC_SUBST([GLPK_LIBS]) + # Check to see if this version/architecture of GNU C++ explicitly # instantiates __gnu_cxx::hash or not. Some do, some don't. # See src/util/hash.h. @@ -1312,6 +1323,7 @@ TLS support : $CVC4_TLS_explanation Portfolio : $with_portfolio MP library : $mplibrary +GLPK : $with_glpk CPPFLAGS : $CPPFLAGS CXXFLAGS : $CXXFLAGS diff --git a/src/Makefile.am b/src/Makefile.am index 1dbf77451..64d95deed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -66,6 +66,11 @@ libcvc4_noinst_la_LIBADD += \ @builddir@/lib/libreplacements.la endif +if CVC4_USE_GLPK +libcvc4_la_LIBADD += $(GLPK_LIBS) +libcvc4_noinst_la_LIBADD += $(GLPK_LIBS) +endif + CLEANFILES = \ svn_versioninfo.cpp \ svninfo.tmp \ diff --git a/src/context/cdhashset.h b/src/context/cdhashset.h index c0fc95637..e2ffac49b 100644 --- a/src/context/cdhashset.h +++ b/src/context/cdhashset.h @@ -61,7 +61,7 @@ public: return super::insert_safe(v, true); } - bool contains(const V& v) { + bool contains(const V& v) const { return super::contains(v); } diff --git a/src/context/cdlist.h b/src/context/cdlist.h index bf3bf5245..47d112cd8 100644 --- a/src/context/cdlist.h +++ b/src/context/cdlist.h @@ -52,6 +52,7 @@ public: /** The value type with which this CDList<> was instantiated. */ typedef T value_type; + /** The cleanup type with which this CDList<> was instantiated. */ typedef CleanUpT CleanUp; /** The allocator type with which this CDList<> was instantiated. */ diff --git a/src/main/options_handlers.h b/src/main/options_handlers.h index 07f19905b..6fe28711b 100644 --- a/src/main/options_handlers.h +++ b/src/main/options_handlers.h @@ -65,6 +65,7 @@ inline void showConfiguration(std::string option, SmtEngine* smt) { printf("\n"); printf("cudd : %s\n", Configuration::isBuiltWithCudd() ? "yes" : "no"); printf("cln : %s\n", Configuration::isBuiltWithCln() ? "yes" : "no"); + printf("glpk : %s\n", Configuration::isBuiltWithGlpk() ? "yes" : "no"); printf("gmp : %s\n", Configuration::isBuiltWithGmp() ? "yes" : "no"); printf("tls : %s\n", Configuration::isBuiltWithTlsSupport() ? "yes" : "no"); exit(0); diff --git a/src/theory/arith/Makefile.am b/src/theory/arith/Makefile.am index 8077148c3..c5b07c3a5 100644 --- a/src/theory/arith/Makefile.am +++ b/src/theory/arith/Makefile.am @@ -10,11 +10,11 @@ libarith_la_SOURCES = \ type_enumerator.h \ arithvar.h \ arithvar.cpp \ + bound_counts.h \ arith_rewriter.h \ arith_rewriter.cpp \ arith_static_learner.h \ arith_static_learner.cpp \ - arithvar_node_map.h \ constraint_forward.h \ constraint.h \ constraint.cpp \ @@ -29,14 +29,34 @@ libarith_la_SOURCES = \ partial_model.cpp \ linear_equality.h \ linear_equality.cpp \ + simplex_update.h \ + simplex_update.cpp \ + callbacks.h \ + callbacks.cpp \ matrix.h \ matrix.cpp \ - arith_priority_queue.h \ - arith_priority_queue.cpp \ + tableau.h \ + tableau.cpp \ + tableau_sizes.h \ + tableau_sizes.cpp \ + error_set.h \ + error_set.cpp \ simplex.h \ simplex.cpp \ + dual_simplex.h \ + dual_simplex.cpp \ + fc_simplex.h \ + fc_simplex.cpp \ + soi_simplex.h \ + soi_simplex.cpp \ + approx_simplex.h \ + approx_simplex.cpp \ + pure_update_simplex.h \ + pure_update_simplex.cpp \ theory_arith.h \ theory_arith.cpp \ + theory_arith_private.h \ + theory_arith_private.cpp \ dio_solver.h \ dio_solver.cpp \ arith_heuristic_pivot_rule.h \ @@ -44,7 +64,7 @@ libarith_la_SOURCES = \ arith_unate_lemma_mode.h \ arith_unate_lemma_mode.cpp \ arith_propagation_mode.h \ - arith_propagation_mode.cpp + arith_propagation_mode.cpp EXTRA_DIST = \ kinds \ diff --git a/src/theory/arith/approx_simplex.cpp b/src/theory/arith/approx_simplex.cpp new file mode 100644 index 000000000..d6be9f657 --- /dev/null +++ b/src/theory/arith/approx_simplex.cpp @@ -0,0 +1,583 @@ +#include "cvc4autoconfig.h" + +#include "theory/arith/approx_simplex.h" +#include "theory/arith/normal_form.h" +#include +#include + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + +ApproximateSimplex::ApproximateSimplex() : + d_pivotLimit(std::numeric_limits::max()) +{} + +void ApproximateSimplex::setPivotLimit(int pivotLimit){ + Assert(pivotLimit >= 0); + d_pivotLimit = pivotLimit; +} + +const double ApproximateSimplex::SMALL_FIXED_DELTA = .000000001; +const double ApproximateSimplex::TOLERENCE = 1 + .000000001; + +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(); + //cout << " cfe["< cfe = rationalToCfe(q,depth); + return cfeToRational(cfe); +} + +Rational ApproximateSimplex::estimateWithCFE(double d){ + return estimateWithCFE(Rational::fromDouble(d), 10); +} + +class ApproxNoOp : public ApproximateSimplex { +public: + ApproxNoOp(const ArithVariables& vars){} + ~ApproxNoOp(){} + + virtual ApproxResult solveRelaxation(){ + return ApproxError; + } + virtual Solution extractRelaxation() const{ + return Solution(); + } + + virtual ApproxResult solveMIP(){ + return ApproxError; + } + virtual Solution extractMIP() const{ + return Solution(); + } + + virtual void setOptCoeffs(const ArithRatPairVec& ref){} +}; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + +/* Begin the declaration of GLPK specific code. */ +#ifdef CVC4_USE_GLPK +extern "C" { +#include +} + +namespace CVC4 { +namespace theory { +namespace arith { + +class ApproxGLPK : public ApproximateSimplex { +private: + glp_prob* d_prob; + const ArithVariables& d_vars; + + DenseMap d_colIndices; + DenseMap d_rowIndices; + + + int d_instanceID; + + bool d_solvedRelaxation; + bool d_solvedMIP; + +public: + ApproxGLPK(const ArithVariables& vars); + ~ApproxGLPK(); + + virtual ApproxResult solveRelaxation(); + virtual Solution extractRelaxation() const{ + return extractSolution(false); + } + + virtual ApproxResult solveMIP(); + virtual Solution extractMIP() const{ + return extractSolution(true); + } + virtual void setOptCoeffs(const ArithRatPairVec& ref); + +private: + Solution extractSolution(bool mip) const; +}; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ +#endif /*#ifdef CVC4_USE_GLPK */ +/* End the declaration of GLPK specific code. */ + +/* Begin GPLK/NOGLPK Glue code. */ +namespace CVC4 { +namespace theory { +namespace arith { +ApproximateSimplex* ApproximateSimplex::mkApproximateSimplexSolver(const ArithVariables& vars){ +#ifdef CVC4_USE_GLPK + return new ApproxGLPK(vars); +#else + return new ApproxNoOp(vars); +#endif +} +bool ApproximateSimplex::enabled() { +#ifdef CVC4_USE_GLPK + return true; +#else + return false; +#endif +} +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ +/* End GPLK/NOGLPK Glue code. */ + + +/* Begin GPLK implementation. */ +#ifdef CVC4_USE_GLPK +namespace CVC4 { +namespace theory { +namespace arith { + +ApproxGLPK::ApproxGLPK(const ArithVariables& avars) : + d_vars(avars), d_solvedRelaxation(false), d_solvedMIP(false) +{ + static int instance = 0; + ++instance; + d_instanceID = instance; + + d_prob = glp_create_prob(); + glp_set_obj_dir(d_prob, GLP_MAX); + glp_set_prob_name(d_prob, "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.isSlack(v)){ + ++numRows; + d_rowIndices.set(v, numRows); + }else{ + ++numCols; + d_colIndices.set(v, numCols); + } + } + glp_add_rows(d_prob, numRows); + glp_add_cols(d_prob, 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; + + //cout << v << " "; + //d_vars.printModel(v, cout); + + 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.isSlack(v)){ + int rowIndex = d_rowIndices[v]; + glp_set_row_bnds(d_prob, rowIndex, type, lb, ub); + }else{ + int colIndex = d_colIndices[v]; + int kind = d_vars.isInteger(v) ? GLP_IV : GLP_CV; + glp_set_col_kind(d_prob, colIndex, kind); + glp_set_col_bnds(d_prob, 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 i = p.begin(), end = p.end(); i != end; ++i){ + ++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 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(); + + 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_prob, numEntries, ia, ja, ar); + + delete[] ia; + delete[] ja; + delete[] ar; +} + +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.isSlack(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_prob, 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 + */ + +static void 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_prob); +} + + +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; + + for(ArithVariables::var_iterator i = d_vars.var_begin(), i_end = d_vars.var_end(); i != i_end; ++i){ + ArithVar vi = *i; + bool isSlack = d_vars.isSlack(vi); + int glpk_index = isSlack ? d_rowIndices[vi] : d_colIndices[vi]; + + int status = isSlack ? glp_get_row_stat(d_prob, glpk_index) : glp_get_col_stat(d_prob, glpk_index); + //cout << "assignment " << vi << endl; + + switch(status){ + case GLP_BS: + //cout << "basic" << endl; + newBasis.add(vi); + break; + case GLP_NL: + case GLP_NS: + if(!mip){ + //cout << "non-basic lb" << endl; + newValues.set(vi, d_vars.getLowerBound(vi)); + break; + }// intentionally fall through otherwise + case GLP_NU: + if(!mip){ + // cout << "non-basic ub" << endl; + newValues.set(vi, d_vars.getUpperBound(vi)); + break; + }// intentionally fall through otherwise + default: + { + // cout << "non-basic other" << endl; + + double newAssign = + mip ? + (isSlack ? glp_mip_row_val(d_prob, glpk_index) : glp_mip_col_val(d_prob, glpk_index)) + : (isSlack ? glp_get_row_prim(d_prob, glpk_index) : glp_get_col_prim(d_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))){ + //cout << " to lb" << endl; + + 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)); + // cout << " to ub" << endl; + }else{ + + double rounded = round(newAssign); + if(roughlyEqual(newAssign, rounded)){ + // cout << "roughly equal " << rounded << " " << newAssign << " " << oldAssign << endl; + newAssign = rounded; + }else{ + // cout << "not roughly equal " << rounded << " " << newAssign << " " << oldAssign << endl; + } + + DeltaRational proposal = estimateWithCFE(newAssign); + + + if(roughlyEqual(newAssign, oldAssign.approx(SMALL_FIXED_DELTA))){ + // cout << " to prev value" << newAssign << " " << oldAssign << endl; + proposal = d_vars.getAssignment(vi); + } + + + if(d_vars.strictlyLessThanLowerBound(vi, proposal)){ + //cout << " round to lb " << d_vars.getLowerBound(vi) << endl; + proposal = d_vars.getLowerBound(vi); + }else if(d_vars.strictlyGreaterThanUpperBound(vi, proposal)){ + //cout << " round to ub " << d_vars.getUpperBound(vi) << endl; + proposal = d_vars.getUpperBound(vi); + }else{ + //cout << " use proposal" << proposal << " " << oldAssign << endl; + } + newValues.set(vi, proposal); + } + break; + } + } + } + return sol; +} + +ApproximateSimplex::ApproxResult 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_ALL; + parm.msg_lev = GLP_MSG_OFF; + + int res = glp_simplex(d_prob, &parm); + switch(res){ + case 0: + { + int status = glp_get_status(d_prob); + switch(status){ + case GLP_OPT: + case GLP_FEAS: + case GLP_UNBND: + d_solvedRelaxation = true; + return ApproxSat; + case GLP_INFEAS: + case GLP_NOFEAS: + d_solvedRelaxation = true; + return ApproxUnsat; + default: + return ApproxError; + } + } + default: + return ApproxError; + } +} + +void stopAtBingoOrPivotLimit(glp_tree *tree, void *info){ + int pivotLimit = *((int*)info); + switch(glp_ios_reason(tree)){ + case GLP_IBINGO: + glp_ios_terminate(tree); + break; + default: + glp_prob* prob = glp_ios_get_prob(tree); + int iterationcount = lpx_get_int_parm(prob, LPX_K_ITCNT); + if(iterationcount > pivotLimit){ + glp_ios_terminate(tree); + } + break; + } +} + +ApproximateSimplex::ApproxResult ApproxGLPK::solveMIP(){ + 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. + 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 = stopAtBingoOrPivotLimit; + parm.cb_info = &d_pivotLimit; + //parm.msg_lev = GLP_MSG_ALL; + parm.msg_lev = GLP_MSG_OFF; + int res = glp_intopt(d_prob, &parm); + + switch(res){ + case 0: + case GLP_ESTOP: + { + int status = glp_mip_status(d_prob); + switch(status){ + case GLP_OPT: + case GLP_FEAS: + d_solvedMIP = true; + return ApproxSat; + case GLP_NOFEAS: + d_solvedMIP = true; + return ApproxUnsat; + default: + return ApproxError; + } + } + default: + return ApproxError; + } +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ +#endif /*#ifdef CVC4_USE_GLPK */ +/* End GPLK implementation. */ diff --git a/src/theory/arith/approx_simplex.h b/src/theory/arith/approx_simplex.h new file mode 100644 index 000000000..a2f3cde24 --- /dev/null +++ b/src/theory/arith/approx_simplex.h @@ -0,0 +1,90 @@ + +#include "cvc4_private.h" + +#pragma once + +#include "util/statistics_registry.h" +#include "theory/arith/arithvar.h" +#include "theory/arith/linear_equality.h" +#include "util/dense_map.h" +#include + +namespace CVC4 { +namespace theory { +namespace arith { + + +class ApproximateSimplex{ +protected: + int d_pivotLimit; + +public: + + static bool enabled(); + + /** + * If glpk is enabled, return a subclass that can do something. + * If glpk is disabled, return a sublass that does nothing. + */ + static ApproximateSimplex* mkApproximateSimplexSolver(const ArithVariables& vars); + ApproximateSimplex(); + virtual ~ApproximateSimplex(){} + + /** A result is either sat, unsat or unknown.*/ + enum ApproxResult {ApproxError, ApproxSat, ApproxUnsat}; + struct Solution { + DenseSet newBasis; + DenseMap newValues; + Solution() : newBasis(), newValues(){} + }; + + /** Sets a deterministic effort limit. */ + void setPivotLimit(int pivotLimit); + + /** Sets a maximization criteria for the approximate solver.*/ + virtual void setOptCoeffs(const ArithRatPairVec& ref) = 0; + + virtual ApproxResult solveRelaxation() = 0; + virtual Solution extractRelaxation() const = 0; + + virtual ApproxResult solveMIP() = 0; + virtual Solution extractMIP() const = 0; + + static void applySolution(LinearEqualityModule& linEq, const Solution& sol){ + linEq.forceNewBasis(sol.newBasis); + linEq.updateMany(sol.newValues); + } + + /** UTILIES FOR DEALING WITH ESTIMATES */ + + static const double SMALL_FIXED_DELTA; + static const double TOLERENCE; + + /** 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 Rational estimateWithCFE(double 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, int depth); +};/* class ApproximateSimplex */ + + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/arith_heuristic_pivot_rule.cpp b/src/theory/arith/arith_heuristic_pivot_rule.cpp index 4fbc84892..62b5fdff1 100644 --- a/src/theory/arith/arith_heuristic_pivot_rule.cpp +++ b/src/theory/arith/arith_heuristic_pivot_rule.cpp @@ -19,16 +19,16 @@ namespace CVC4 { -std::ostream& operator<<(std::ostream& out, ArithHeuristicPivotRule rule) { +std::ostream& operator<<(std::ostream& out, ErrorSelectionRule rule) { switch(rule) { - case MINIMUM: - out << "MINIMUM"; + case MINIMUM_AMOUNT: + out << "MINIMUM_AMOUNT"; break; - case BREAK_TIES: - out << "BREAK_TIES"; + case VAR_ORDER: + out << "VAR_ORDER"; break; - case MAXIMUM: - out << "MAXIMUM"; + case MAXIMUM_AMOUNT: + out << "MAXIMUM_AMOUNT"; break; default: out << "ArithHeuristicPivotRule!UNKNOWN"; diff --git a/src/theory/arith/arith_heuristic_pivot_rule.h b/src/theory/arith/arith_heuristic_pivot_rule.h index a1ecc5b2c..705c3e2f3 100644 --- a/src/theory/arith/arith_heuristic_pivot_rule.h +++ b/src/theory/arith/arith_heuristic_pivot_rule.h @@ -25,12 +25,13 @@ namespace CVC4 { typedef enum { - MINIMUM, - BREAK_TIES, - MAXIMUM -} ArithHeuristicPivotRule; + VAR_ORDER, + MINIMUM_AMOUNT, + MAXIMUM_AMOUNT, + SUM_METRIC +} ErrorSelectionRule; -std::ostream& operator<<(std::ostream& out, ArithHeuristicPivotRule rule) CVC4_PUBLIC; +std::ostream& operator<<(std::ostream& out, ErrorSelectionRule rule) CVC4_PUBLIC; }/* CVC4 namespace */ diff --git a/src/theory/arith/arith_priority_queue.cpp b/src/theory/arith/arith_priority_queue.cpp deleted file mode 100644 index 9fc30c136..000000000 --- a/src/theory/arith/arith_priority_queue.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/********************* */ -/*! \file arith_priority_queue.cpp - ** \verbatim - ** Original author: Tim King - ** Major contributors: Morgan Deters - ** Minor contributors (to current version): none - ** This file is part of the CVC4 project. - ** Copyright (c) 2009-2013 New York University and The University of Iowa - ** See the file COPYING in the top-level source directory for licensing - ** information.\endverbatim - ** - ** \brief [[ Add one-line brief description here ]] - ** - ** [[ Add lengthier description here ]] - ** \todo document this file - **/ - - -#include "theory/arith/arith_priority_queue.h" - -#include - -using namespace std; - -using namespace CVC4; -using namespace CVC4::kind; - -using namespace CVC4::theory; -using namespace CVC4::theory::arith; - -ArithPriorityQueue::Statistics::Statistics(): - d_enqueues("theory::arith::pqueue::enqueues", 0), - d_enqueuesCollection("theory::arith::pqueue::enqueuesCollection", 0), - d_enqueuesDiffMode("theory::arith::pqueue::enqueuesDiffMode", 0), - d_enqueuesVarOrderMode("theory::arith::pqueue::enqueuesVarOrderMode", 0), - d_enqueuesCollectionDuplicates("theory::arith::pqueue::enqueuesCollectionDuplicates", 0), - d_enqueuesVarOrderModeDuplicates("theory::arith::pqueue::enqueuesVarOrderModeDuplicates", 0) -{ - StatisticsRegistry::registerStat(&d_enqueues); - StatisticsRegistry::registerStat(&d_enqueuesCollection); - StatisticsRegistry::registerStat(&d_enqueuesDiffMode); - StatisticsRegistry::registerStat(&d_enqueuesVarOrderMode); - StatisticsRegistry::registerStat(&d_enqueuesCollectionDuplicates); - StatisticsRegistry::registerStat(&d_enqueuesVarOrderModeDuplicates); -} - -ArithPriorityQueue::Statistics::~Statistics(){ - StatisticsRegistry::unregisterStat(&d_enqueues); - StatisticsRegistry::unregisterStat(&d_enqueuesCollection); - StatisticsRegistry::unregisterStat(&d_enqueuesDiffMode); - StatisticsRegistry::unregisterStat(&d_enqueuesVarOrderMode); - StatisticsRegistry::unregisterStat(&d_enqueuesCollectionDuplicates); - StatisticsRegistry::unregisterStat(&d_enqueuesVarOrderModeDuplicates); -} - -ArithPriorityQueue::ArithPriorityQueue(ArithPartialModel& pm, const Tableau& tableau): - d_pivotRule(MINIMUM), - d_partialModel(pm), - d_tableau(tableau), - d_modeInUse(Collection), - d_ZERO_DELTA(0,0) -{} - -void ArithPriorityQueue::setPivotRule(PivotRule rule){ - Assert(!inDifferenceMode()); - Debug("arith::setPivotRule") << "setting pivot rule " << rule << endl; - d_pivotRule = rule; -} - -ArithVar ArithPriorityQueue::dequeueInconsistentBasicVariable(){ - AlwaysAssert(!inCollectionMode()); - - Debug("arith_update") << "dequeueInconsistentBasicVariable()" << endl; - - if(inDifferenceMode()){ - while(!d_diffQueue.empty()){ - ArithVar var = d_diffQueue.front().variable(); - switch(d_pivotRule){ - case MINIMUM: - pop_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::minimumRule); - break; - case BREAK_TIES: - pop_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::breakTiesRules); - break; - case MAXIMUM: - pop_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::maximumRule); - break; - } - d_diffQueue.pop_back(); - Debug("arith_update") << "possiblyInconsistentGriggio var" << var << endl; - if(basicAndInconsistent(var)){ - return var; - } - } - }else{ - Assert(inVariableOrderMode()); - Debug("arith_update") << "possiblyInconsistent.size()" - << d_varOrderQueue.size() << endl; - - while(!d_varOrderQueue.empty()){ - ArithVar var = d_varOrderQueue.front(); - pop_heap(d_varOrderQueue.begin(), d_varOrderQueue.end(), std::greater()); - d_varOrderQueue.pop_back(); - - d_varSet.remove(var); - - Debug("arith_update") << "possiblyInconsistent var" << var << endl; - if(basicAndInconsistent(var)){ - return var; - } - } - } - return ARITHVAR_SENTINEL; -} - -ArithPriorityQueue::VarDRatPair ArithPriorityQueue::computeDiff(ArithVar basic){ - Assert(basicAndInconsistent(basic)); - const DeltaRational& beta = d_partialModel.getAssignment(basic); - DeltaRational diff = d_partialModel.strictlyLessThanLowerBound(basic,beta) ? - d_partialModel.getLowerBound(basic) - beta: - beta - d_partialModel.getUpperBound(basic); - - Assert(d_ZERO_DELTA < diff); - return VarDRatPair(basic,diff); -} - -void ArithPriorityQueue::enqueueIfInconsistent(ArithVar basic){ - Assert(d_tableau.isBasic(basic)); - - if(basicAndInconsistent(basic)){ - ++d_statistics.d_enqueues; - - switch(d_modeInUse){ - case Collection: - ++d_statistics.d_enqueuesCollection; - if(!d_varSet.isMember(basic)){ - d_varSet.add(basic); - d_candidates.push_back(basic); - }else{ - ++d_statistics.d_enqueuesCollectionDuplicates; - } - break; - case VariableOrder: - ++d_statistics.d_enqueuesVarOrderMode; - if(!d_varSet.isMember(basic)){ - d_varSet.add(basic); - d_varOrderQueue.push_back(basic); - push_heap(d_varOrderQueue.begin(), d_varOrderQueue.end(), std::greater()); - }else{ - ++d_statistics.d_enqueuesVarOrderModeDuplicates; - } - break; - case Difference: - ++d_statistics.d_enqueuesDiffMode; - d_diffQueue.push_back(computeDiff(basic)); - switch(d_pivotRule){ - case MINIMUM: - push_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::minimumRule); - break; - case BREAK_TIES: - push_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::breakTiesRules); - break; - case MAXIMUM: - push_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::maximumRule); - break; - } - break; - default: - Unreachable(); - } - } -} - -void ArithPriorityQueue::transitionToDifferenceMode() { - Assert(inCollectionMode()); - Assert(d_varOrderQueue.empty()); - Assert(d_diffQueue.empty()); - - Debug("arith::priorityqueue") << "transitionToDifferenceMode()" << endl; - d_varSet.purge(); - - ArithVarArray::const_iterator i = d_candidates.begin(), end = d_candidates.end(); - for(; i != end; ++i){ - ArithVar var = *i; - if(basicAndInconsistent(var)){ - d_diffQueue.push_back(computeDiff(var)); - } - } - - switch(d_pivotRule){ - case MINIMUM: - Debug("arith::pivotRule") << "Making the minimum heap." << endl; - make_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::minimumRule); - break; - case BREAK_TIES: - Debug("arith::pivotRule") << "Making the break ties heap." << endl; - make_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::breakTiesRules); - break; - case MAXIMUM: - Debug("arith::pivotRule") << "Making the maximum heap." << endl; - make_heap(d_diffQueue.begin(), d_diffQueue.end(), VarDRatPair::maximumRule); - break; - } - - d_candidates.clear(); - d_modeInUse = Difference; - - Assert(d_varSet.empty()); - Assert(inDifferenceMode()); - Assert(d_varOrderQueue.empty()); - Assert(d_candidates.empty()); -} - -void ArithPriorityQueue::transitionToVariableOrderMode() { - Assert(inDifferenceMode()); - Assert(d_varOrderQueue.empty()); - Assert(d_candidates.empty()); - Assert(d_varSet.empty()); - - Debug("arith::priorityqueue") << "transitionToVariableOrderMode()" << endl; - - DifferenceArray::const_iterator i = d_diffQueue.begin(), end = d_diffQueue.end(); - for(; i != end; ++i){ - ArithVar var = (*i).variable(); - if(basicAndInconsistent(var) && !d_varSet.isMember(var)){ - d_varSet.add(var); - d_varOrderQueue.push_back(var); - } - } - make_heap(d_varOrderQueue.begin(), d_varOrderQueue.end(), std::greater()); - d_diffQueue.clear(); - d_modeInUse = VariableOrder; - - Assert(inVariableOrderMode()); - Assert(d_diffQueue.empty()); - Assert(d_candidates.empty()); -} - -void ArithPriorityQueue::transitionToCollectionMode() { - Assert(inDifferenceMode() || inVariableOrderMode()); - Assert(d_candidates.empty()); - - if(inDifferenceMode()){ - Assert(d_varSet.empty()); - Assert(d_varOrderQueue.empty()); - Assert(inDifferenceMode()); - - DifferenceArray::const_iterator i = d_diffQueue.begin(), end = d_diffQueue.end(); - for(; i != end; ++i){ - ArithVar var = (*i).variable(); - if(basicAndInconsistent(var) && !d_varSet.isMember(var)){ - d_candidates.push_back(var); - d_varSet.add(var); - } - } - d_diffQueue.clear(); - }else{ - Assert(d_diffQueue.empty()); - Assert(inVariableOrderMode()); - - d_varSet.purge(); - - ArithVarArray::const_iterator i = d_varOrderQueue.begin(), end = d_varOrderQueue.end(); - for(; i != end; ++i){ - ArithVar var = *i; - if(basicAndInconsistent(var)){ - d_candidates.push_back(var); - d_varSet.add(var); // cannot have duplicates. - } - } - d_varOrderQueue.clear(); - } - - Assert(d_diffQueue.empty()); - Assert(d_varOrderQueue.empty()); - - Debug("arith::priorityqueue") << "transitionToCollectionMode()" << endl; - - d_modeInUse = Collection; -} - -void ArithPriorityQueue::clear(){ - switch(d_modeInUse){ - case Collection: - d_candidates.clear(); - d_varSet.purge(); - break; - case VariableOrder: - if(!d_varOrderQueue.empty()) { - d_varOrderQueue.clear(); - d_varSet.purge(); - } - break; - case Difference: - if(!d_diffQueue.empty()){ - d_diffQueue.clear(); - d_varSet.purge(); - } - break; - default: - Unreachable(); - } - - Assert(d_varSet.empty()); - Assert(d_candidates.empty()); - Assert(d_varOrderQueue.empty()); - Assert(d_diffQueue.empty()); -} - -std::ostream& CVC4::theory::arith::operator<<(std::ostream& out, ArithPriorityQueue::PivotRule rule) { - switch(rule) { - case ArithPriorityQueue::MINIMUM: - out << "MINIMUM"; - break; - case ArithPriorityQueue::BREAK_TIES: - out << "BREAK_TIES"; - break; - case ArithPriorityQueue::MAXIMUM: - out << "MAXIMUM"; - break; - default: - out << "PivotRule!UNKNOWN"; - } - - return out; -} - -void ArithPriorityQueue::reduce(){ - vector contents; - - if(inCollectionMode()){ - contents = d_candidates; - } else { - ArithVar res = ARITHVAR_SENTINEL; - while((res = dequeueInconsistentBasicVariable()) != ARITHVAR_SENTINEL){ - contents.push_back(res); - } - } - clear(); - for(vector::const_iterator iter = contents.begin(), end = contents.end(); iter != end; ++iter){ - ArithVar curr = *iter; - if(d_tableau.isBasic(curr)){ - enqueueIfInconsistent(curr); - } - } -} diff --git a/src/theory/arith/arith_priority_queue.h b/src/theory/arith/arith_priority_queue.h deleted file mode 100644 index 912799dde..000000000 --- a/src/theory/arith/arith_priority_queue.h +++ /dev/null @@ -1,337 +0,0 @@ -/********************* */ -/*! \file arith_priority_queue.h - ** \verbatim - ** Original author: Tim King - ** Major contributors: none - ** Minor contributors (to current version): Morgan Deters - ** This file is part of the CVC4 project. - ** Copyright (c) 2009-2013 New York University and The University of Iowa - ** See the file COPYING in the top-level source directory for licensing - ** information.\endverbatim - ** - ** \brief [[ Add one-line brief description here ]] - ** - ** [[ Add lengthier description here ]] - ** \todo document this file - **/ - - -#include "cvc4_private.h" - -#ifndef __CVC4__THEORY__ARITH__ARITH_PRIORITY_QUEUE_H -#define __CVC4__THEORY__ARITH__ARITH_PRIORITY_QUEUE_H - -#include "theory/arith/arithvar.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/matrix.h" -#include "theory/arith/partial_model.h" - -#include "util/statistics_registry.h" - -#include - -namespace CVC4 { -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 ArithPriorityQueue { -public: - enum PivotRule {MINIMUM, BREAK_TIES, MAXIMUM}; - -private: - class VarDRatPair { - private: - ArithVar d_variable; - DeltaRational d_orderBy; - public: - VarDRatPair(ArithVar var, const DeltaRational& dr): - d_variable(var), d_orderBy(dr) - { } - - ArithVar variable() const { - return d_variable; - } - - static bool minimumRule(const VarDRatPair& a, const VarDRatPair& b){ - return a.d_orderBy > b.d_orderBy; - } - static bool maximumRule(const VarDRatPair& a, const VarDRatPair& b){ - return a.d_orderBy < b.d_orderBy; - } - - static bool breakTiesRules(const VarDRatPair& a, const VarDRatPair& b){ - const Rational& nonInfA = a.d_orderBy.getNoninfinitesimalPart(); - const Rational& nonInfB = b.d_orderBy.getNoninfinitesimalPart(); - int cmpNonInf = nonInfA.cmp(nonInfB); - if(cmpNonInf == 0){ - const Rational& infA = a.d_orderBy.getInfinitesimalPart(); - const Rational& infB = b.d_orderBy.getInfinitesimalPart(); - int cmpInf = infA.cmp(infB); - if(cmpInf == 0){ - return a.d_variable > b.d_variable; - }else{ - return cmpInf > 0; - } - }else{ - return cmpNonInf > 0; - } - - return a.d_orderBy > b.d_orderBy; - } - }; - - typedef std::vector DifferenceArray; - typedef std::vector ArithVarArray; - - PivotRule d_pivotRule; - - /** - * An unordered array with no heap structure for use during collection mode. - */ - ArithVarArray d_candidates; - - /** - * Priority Queue of the basic variables that may be inconsistent. - * Variables are ordered according to which violates its bound the most. - * This is a heuristic and makes no guarantees to terminate! - * This heuristic comes from Alberto Griggio's thesis. - */ - DifferenceArray d_diffQueue; - - /** - * Priority Queue of the basic variables that may be inconsistent. - * - * This is required to contain at least 1 instance of every inconsistent - * basic variable. This is only required to be a superset though so its - * contents must be checked to still be basic and inconsistent. - * - * This is also required to agree with the row on variable order for termination. - * Effectively this means that this must be a min-heap. - */ - ArithVarArray d_varOrderQueue; - - /** - * A superset of the basic variables that may be inconsistent. - * This is empty during DiffOrderMode, and otherwise it is the same set as candidates - * or varOrderQueue. - */ - DenseSet d_varSet; - - /** - * Reference to the arithmetic partial model for checking if a variable - * is consistent with its upper and lower bounds. - */ - ArithPartialModel& d_partialModel; - - /** Reference to the Tableau for checking if a variable is basic. */ - const Tableau& d_tableau; - - enum Mode {Collection, Difference, VariableOrder}; - /** - * Controls which priority queue is in use. - * If true, d_griggioRuleQueue is used. - * If false, d_possiblyInconsistent is used. - */ - Mode d_modeInUse; - - /** Storage of Delta Rational 0 */ - DeltaRational d_ZERO_DELTA; - - VarDRatPair computeDiff(ArithVar basic); - -public: - - ArithPriorityQueue(ArithPartialModel& pm, const Tableau& tableau); - - /** precondition: !inDifferenceMode() */ - void setPivotRule(PivotRule rule); - - ArithVar dequeueInconsistentBasicVariable(); - - void enqueueIfInconsistent(ArithVar basic); - - inline bool basicAndInconsistent(ArithVar var) const{ - return d_tableau.isBasic(var) - && !d_partialModel.assignmentIsConsistent(var) ; - } - - void transitionToDifferenceMode(); - void transitionToVariableOrderMode(); - void transitionToCollectionMode(); - - inline bool inDifferenceMode() const{ - return d_modeInUse == Difference; - } - inline bool inCollectionMode() const{ - return d_modeInUse == Collection; - } - inline bool inVariableOrderMode() const{ - return d_modeInUse == VariableOrder; - } - - inline bool empty() const{ - switch(d_modeInUse){ - case Collection: return d_candidates.empty(); - case VariableOrder: return d_varOrderQueue.empty(); - case Difference: return d_diffQueue.empty(); - default: Unreachable(); - } - } - - inline size_t size() const { - switch(d_modeInUse){ - case Collection: return d_candidates.size(); - case VariableOrder: return d_varOrderQueue.size(); - case Difference: return d_diffQueue.size(); - default: Unreachable(); - } - } - - /** Clears the queue. */ - void clear(); - - - /** - * Reduces the queue to only contain the subset that is still basic - * and inconsistent. - *Currently, O(n log n) for an easy obviously correct implementation in all modes.. - */ - void reduce(); - - bool collectionModeContains(ArithVar v) const { - Assert(inCollectionMode()); - return d_varSet.isMember(v); - } - - class const_iterator { - private: - Mode d_mode; - ArithVarArray::const_iterator d_avIter; - DifferenceArray::const_iterator d_diffIter; - public: - const_iterator(Mode m, - ArithVarArray::const_iterator av, - DifferenceArray::const_iterator diff): - d_mode(m), d_avIter(av), d_diffIter(diff) - {} - const_iterator(const const_iterator& other): - d_mode(other.d_mode), d_avIter(other.d_avIter), d_diffIter(other.d_diffIter) - {} - bool operator==(const const_iterator& other) const{ - AlwaysAssert(d_mode == other.d_mode); - switch(d_mode){ - case Collection: - case VariableOrder: - return d_avIter == other.d_avIter; - case Difference: - return d_diffIter == other.d_diffIter; - default: - Unreachable(); - } - } - bool operator!=(const const_iterator& other) const{ - return !(*this == other); - } - const_iterator& operator++(){ - switch(d_mode){ - case Collection: - case VariableOrder: - ++d_avIter; - break; - case Difference: - ++d_diffIter; - break; - default: - Unreachable(); - } - return *this; - } - - ArithVar operator*() const{ - switch(d_mode){ - case Collection: - case VariableOrder: - return *d_avIter; - case Difference: - return (*d_diffIter).variable(); - default: - Unreachable(); - } - } - }; - - const_iterator begin() const{ - switch(d_modeInUse){ - case Collection: - return const_iterator(Collection, d_candidates.begin(), d_diffQueue.end()); - case VariableOrder: - return const_iterator(VariableOrder, d_varOrderQueue.begin(), d_diffQueue.end()); - case Difference: - return const_iterator(Difference, d_varOrderQueue.end(), d_diffQueue.begin()); - default: - Unreachable(); - } - } - - const_iterator end() const{ - switch(d_modeInUse){ - case Collection: - return const_iterator(Collection, d_candidates.end(), d_diffQueue.end()); - case VariableOrder: - return const_iterator(VariableOrder, d_varOrderQueue.end(), d_diffQueue.end()); - case Difference: - return const_iterator(Difference, d_varOrderQueue.end(), d_diffQueue.end()); - default: - Unreachable(); - } - } - -private: - class Statistics { - public: - IntStat d_enqueues; - IntStat d_enqueuesCollection; - IntStat d_enqueuesDiffMode; - IntStat d_enqueuesVarOrderMode; - - IntStat d_enqueuesCollectionDuplicates; - IntStat d_enqueuesVarOrderModeDuplicates; - - Statistics(); - ~Statistics(); - }; - - Statistics d_statistics; -}; - -std::ostream& operator<<(std::ostream& out, ArithPriorityQueue::PivotRule rule); - -}/* CVC4::theory::arith namespace */ -}/* CVC4::theory namespace */ -}/* CVC4 namespace */ - -#endif /* __CVC4__THEORY__ARITH_PRIORITY_QUEUE_H */ diff --git a/src/theory/arith/arith_static_learner.cpp b/src/theory/arith/arith_static_learner.cpp index a4dfb1fa4..bf5261439 100644 --- a/src/theory/arith/arith_static_learner.cpp +++ b/src/theory/arith/arith_static_learner.cpp @@ -182,6 +182,12 @@ void ArithStaticLearner::iteMinMax(TNode n, NodeBuilder<>& learned){ 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 ); diff --git a/src/theory/arith/arith_utilities.h b/src/theory/arith/arith_utilities.h index 2efd895f2..6b297f2ab 100644 --- a/src/theory/arith/arith_utilities.h +++ b/src/theory/arith/arith_utilities.h @@ -21,8 +21,10 @@ #include "util/rational.h" #include "util/integer.h" +#include "util/dense_map.h" #include "expr/node.h" #include "theory/arith/delta_rational.h" +#include "theory/arith/arithvar.h" #include "context/cdhashset.h" #include #include @@ -36,6 +38,10 @@ typedef __gnu_cxx::hash_set NodeSet; typedef __gnu_cxx::hash_set TNodeSet; typedef context::CDHashSet CDNodeSet; +//Maps from Nodes -> ArithVars, and vice versa +typedef __gnu_cxx::hash_map NodeToArithVarMap; +typedef DenseMap ArithVarToNodeMap; + inline Node mkRationalNode(const Rational& q){ return NodeManager::currentNM()->mkConst(q); } @@ -52,6 +58,27 @@ inline Node mkRealSkolem(const std::string& name){ return NodeManager::currentNM()->mkSkolem(name, NodeManager::currentNM()->realType()); } +inline Node skolemFunction(const std::string& name, TypeNode dom, TypeNode range){ + NodeManager* currNM = NodeManager::currentNM(); + TypeNode functionType = currNM->mkFunctionType(dom, range); + return currNM->mkSkolem(name, functionType); +} + +/** + * (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 ArithType { + ATReal = 0, + ATInteger = 1 +}; + +inline ArithType nodeToArithType(TNode x) { + return (x.getType().isInteger() ? ATInteger : ATReal); +} + /** \f$ k \in {LT, LEQ, EQ, GEQ, GT} \f$ */ inline bool isRelationOperator(Kind k){ using namespace kind; diff --git a/src/theory/arith/arithvar.h b/src/theory/arith/arithvar.h index e6cecff3f..66b68339d 100644 --- a/src/theory/arith/arithvar.h +++ b/src/theory/arith/arithvar.h @@ -19,15 +19,12 @@ #include "cvc4_private.h" -#ifndef __CVC4__THEORY__ARITH__ARITHVAR_H -#define __CVC4__THEORY__ARITH__ARITHVAR_H - -#include -#include "expr/node.h" -#include "context/cdhashset.h" +#pragma once +#include #include "util/index.h" -#include "util/dense_map.h" +#include "util/rational.h" + namespace CVC4 { namespace theory { @@ -36,46 +33,11 @@ namespace arith { typedef Index ArithVar; extern const ArithVar ARITHVAR_SENTINEL; -//Maps from Nodes -> ArithVars, and vice versa -typedef __gnu_cxx::hash_map NodeToArithVarMap; -typedef DenseMap ArithVarToNodeMap; - -/** - * ArithVarCallBack provides a mechanism for agreeing on callbacks while - * breaking mutual recursion inclusion order problems. - */ -class ArithVarCallBack { -public: - 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 ArithVar request() = 0; - virtual void release(ArithVar v) = 0; -}; - -class TNodeCallBack { -public: - virtual void operator()(TNode n) = 0; -}; - -class NodeCallBack { -public: - virtual void operator()(Node n) = 0; -}; - -class RationalCallBack { -public: - virtual Rational operator()() const = 0; -}; +typedef std::vector ArithVarVec; +typedef std::pair ArithRatPair; +typedef std::vector< ArithRatPair > ArithRatPairVec; }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ -#endif /* __CVC4__THEORY__ARITH__ARITHVAR_H */ diff --git a/src/theory/arith/bound_counts.h b/src/theory/arith/bound_counts.h new file mode 100644 index 000000000..954cc056a --- /dev/null +++ b/src/theory/arith/bound_counts.h @@ -0,0 +1,144 @@ +#include "cvc4_private.h" +#pragma once + +#include +#include "theory/arith/arithvar.h" +#include "util/cvc4_assert.h" +#include "util/dense_map.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +/** + * 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)} + */ +class BoundCounts { +private: + uint32_t d_atLowerBounds; + uint32_t d_atUpperBounds; + +public: + BoundCounts() : d_atLowerBounds(0), d_atUpperBounds(0) {} + BoundCounts(uint32_t lbs, uint32_t ubs) + : d_atLowerBounds(lbs), d_atUpperBounds(ubs) {} + + bool operator==(BoundCounts bc) const { + return d_atLowerBounds == bc.d_atLowerBounds + && d_atUpperBounds == bc.d_atUpperBounds; + } + bool operator!=(BoundCounts bc) const { + return d_atLowerBounds != bc.d_atLowerBounds + || d_atUpperBounds != bc.d_atUpperBounds; + } + inline bool isZero() const{ return d_atLowerBounds == 0 && d_atUpperBounds == 0; } + inline uint32_t atLowerBounds() const{ + return d_atLowerBounds; + } + inline uint32_t atUpperBounds() const{ + return d_atUpperBounds; + } + + inline BoundCounts operator+(BoundCounts bc) const{ + return BoundCounts(d_atLowerBounds + bc.d_atLowerBounds, + d_atUpperBounds + bc.d_atUpperBounds); + } + + inline BoundCounts operator-(BoundCounts bc) const { + Assert(d_atLowerBounds >= bc.d_atLowerBounds); + Assert(d_atUpperBounds >= bc.d_atUpperBounds); + return BoundCounts(d_atLowerBounds - bc.d_atLowerBounds, + d_atUpperBounds - bc.d_atUpperBounds); + } + + inline void addInChange(int sgn, BoundCounts before, BoundCounts after){ + Assert(before != after); + if(sgn < 0){ + Assert(d_atUpperBounds >= before.d_atLowerBounds); + Assert(d_atLowerBounds >= before.d_atUpperBounds); + d_atUpperBounds += after.d_atLowerBounds - before.d_atLowerBounds; + d_atLowerBounds += after.d_atUpperBounds - before.d_atUpperBounds; + }else if(sgn > 0){ + Assert(d_atUpperBounds >= before.d_atUpperBounds); + Assert(d_atLowerBounds >= before.d_atLowerBounds); + d_atUpperBounds += after.d_atUpperBounds - before.d_atUpperBounds; + d_atLowerBounds += after.d_atLowerBounds - before.d_atLowerBounds; + } + } + + inline void addInSgn(BoundCounts bc, int before, int after){ + Assert(before != after); + Assert(!bc.isZero()); + + if(before < 0){ + d_atUpperBounds -= bc.d_atLowerBounds; + d_atLowerBounds -= bc.d_atUpperBounds; + }else if(before > 0){ + d_atUpperBounds -= bc.d_atUpperBounds; + d_atLowerBounds -= bc.d_atLowerBounds; + } + if(after < 0){ + d_atUpperBounds += bc.d_atLowerBounds; + d_atLowerBounds += bc.d_atUpperBounds; + }else if(after > 0){ + d_atUpperBounds += bc.d_atUpperBounds; + d_atLowerBounds += bc.d_atLowerBounds; + } + } + + inline BoundCounts& operator+=(BoundCounts bc) { + d_atUpperBounds += bc.d_atUpperBounds; + d_atLowerBounds += bc.d_atLowerBounds; + return *this; + } + + inline BoundCounts& operator-=(BoundCounts bc) { + Assert(d_atLowerBounds >= bc.d_atLowerBounds); + Assert(d_atUpperBounds >= bc.d_atUpperBounds); + d_atUpperBounds -= bc.d_atUpperBounds; + d_atLowerBounds -= bc.d_atLowerBounds; + + return *this; + } + + inline BoundCounts multiplyBySgn(int sgn) const{ + if(sgn > 0){ + return *this; + }else if(sgn == 0){ + return BoundCounts(0,0); + }else{ + return BoundCounts(d_atUpperBounds, d_atLowerBounds); + } + } +}; + +typedef DenseMap BoundCountingVector; + +class BoundCountingLookup { +private: + BoundCountingVector* d_bc; +public: + BoundCountingLookup(BoundCountingVector* bc) : d_bc(bc) {} + BoundCounts boundCounts(ArithVar v) const { + Assert(d_bc->isKey(v)); + return (*d_bc)[v]; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const BoundCounts& bc){ + os << "[bc " << bc.atLowerBounds() << ", " + << bc.atUpperBounds() << "]"; + return os; +} + +class BoundCountsCallback { +public: + virtual void operator()(ArithVar v, BoundCounts bc) = 0; +}; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/callbacks.cpp b/src/theory/arith/callbacks.cpp new file mode 100644 index 000000000..6b6170b20 --- /dev/null +++ b/src/theory/arith/callbacks.cpp @@ -0,0 +1,37 @@ +#include "theory/arith/callbacks.h" +#include "theory/arith/theory_arith_private.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +void SetupLiteralCallBack::operator()(TNode lit){ + TNode atom = (lit.getKind() == kind::NOT) ? lit[0] : lit; + if(!d_arith.isSetup(atom)){ + d_arith.setupAtom(atom); + } +} + +Rational DeltaComputeCallback::operator()() const{ + return d_ta.deltaValueForTotalOrder(); +} + +ArithVar TempVarMalloc::request(){ + Node skolem = mkRealSkolem("tmpVar"); + return d_ta.requestArithVar(skolem, false); +} +void TempVarMalloc::release(ArithVar v){ + d_ta.releaseArithVar(v); +} + +void BasicVarModelUpdateCallBack::operator()(ArithVar x){ + d_ta.signal(x); +} + +void RaiseConflict::operator()(Node n){ + d_ta.raiseConflict(n); +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/callbacks.h b/src/theory/arith/callbacks.h new file mode 100644 index 000000000..0d754d159 --- /dev/null +++ b/src/theory/arith/callbacks.h @@ -0,0 +1,92 @@ + +#pragma once + +#include "expr/node.h" +#include "util/rational.h" +#include "context/cdlist.h" + +#include "theory/arith/theory_arith_private_forward.h" +#include "theory/arith/arithvar.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +/** + * ArithVarCallBack provides a mechanism for agreeing on callbacks while + * breaking mutual recursion inclusion order problems. + */ +class ArithVarCallBack { +public: + 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 ArithVar request() = 0; + virtual void release(ArithVar v) = 0; +}; + +class TNodeCallBack { +public: + virtual void operator()(TNode n) = 0; +}; + +class NodeCallBack { +public: + virtual void operator()(Node n) = 0; +}; + +class RationalCallBack { +public: + virtual Rational operator()() const = 0; +}; + +class SetupLiteralCallBack : public TNodeCallBack { +private: + TheoryArithPrivate& d_arith; +public: + SetupLiteralCallBack(TheoryArithPrivate& ta) : d_arith(ta){} + void operator()(TNode lit); +}; + +class DeltaComputeCallback : public RationalCallBack { +private: + const TheoryArithPrivate& d_ta; +public: + DeltaComputeCallback(const TheoryArithPrivate& ta) : d_ta(ta){} + Rational operator()() const; +}; + +class BasicVarModelUpdateCallBack : public ArithVarCallBack{ +private: + TheoryArithPrivate& d_ta; +public: + BasicVarModelUpdateCallBack(TheoryArithPrivate& ta) : d_ta(ta) {} + void operator()(ArithVar x); +}; + +class TempVarMalloc : public ArithVarMalloc { +private: + TheoryArithPrivate& d_ta; +public: + TempVarMalloc(TheoryArithPrivate& ta) : d_ta(ta) {} + ArithVar request(); + void release(ArithVar v); +}; + +class RaiseConflict : public NodeCallBack { +private: + TheoryArithPrivate& d_ta; +public: + RaiseConflict(TheoryArithPrivate& ta) : d_ta(ta) {} + void operator()(Node n); +}; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/congruence_manager.cpp b/src/theory/arith/congruence_manager.cpp index c10d59234..e3e9055a7 100644 --- a/src/theory/arith/congruence_manager.cpp +++ b/src/theory/arith/congruence_manager.cpp @@ -24,7 +24,7 @@ namespace CVC4 { namespace theory { namespace arith { -ArithCongruenceManager::ArithCongruenceManager(context::Context* c, ConstraintDatabase& cd, TNodeCallBack& setup, const ArithVarNodeMap& av2Node, NodeCallBack& raiseConflict) +ArithCongruenceManager::ArithCongruenceManager(context::Context* c, ConstraintDatabase& cd, SetupLiteralCallBack setup, const ArithVariables& avars, RaiseConflict raiseConflict) : d_inConflict(c), d_raiseConflict(raiseConflict), d_notify(*this), @@ -33,7 +33,7 @@ ArithCongruenceManager::ArithCongruenceManager(context::Context* c, ConstraintDa d_explanationMap(c), d_constraintDatabase(cd), d_setupLiteral(setup), - d_av2Node(av2Node), + d_avariables(avars), d_ee(d_notify, c, "theory::arith::ArithCongruenceManager") {} @@ -295,7 +295,7 @@ void ArithCongruenceManager::equalsConstant(Constraint c){ Debug("equalsConstant") << "equals constant " << c << std::endl; ArithVar x = c->getVariable(); - Node xAsNode = d_av2Node.asNode(x); + Node xAsNode = d_avariables.asNode(x); Node asRational = mkRationalNode(c->getValue().getNoninfinitesimalPart()); @@ -321,7 +321,7 @@ void ArithCongruenceManager::equalsConstant(Constraint lb, Constraint ub){ ArithVar x = lb->getVariable(); Node reason = ConstraintValue::explainConflict(lb,ub); - Node xAsNode = d_av2Node.asNode(x); + Node xAsNode = d_avariables.asNode(x); Node asRational = mkRationalNode(lb->getValue().getNoninfinitesimalPart()); //No guarentee this is in normal form! diff --git a/src/theory/arith/congruence_manager.h b/src/theory/arith/congruence_manager.h index b0a4467bf..b4e009169 100644 --- a/src/theory/arith/congruence_manager.h +++ b/src/theory/arith/congruence_manager.h @@ -20,8 +20,8 @@ #pragma once #include "theory/arith/arithvar.h" -#include "theory/arith/arithvar_node_map.h" #include "theory/arith/constraint_forward.h" +#include "theory/arith/partial_model.h" #include "theory/uf/equality_engine.h" @@ -41,7 +41,7 @@ namespace arith { class ArithCongruenceManager { private: context::CDRaised d_inConflict; - NodeCallBack& d_raiseConflict; + RaiseConflict d_raiseConflict; /** * The set of ArithVars equivalent to a pair of terms. @@ -111,9 +111,9 @@ private: ExplainMap d_explanationMap; ConstraintDatabase& d_constraintDatabase; - TNodeCallBack& d_setupLiteral; + SetupLiteralCallBack d_setupLiteral; - const ArithVarNodeMap& d_av2Node; + const ArithVariables& d_avariables; eq::EqualityEngine d_ee; @@ -181,23 +181,8 @@ private: bool propagate(TNode x); void explain(TNode literal, std::vector& assumptions); - /** - * This is set to true when the first shared term is added. - * When this is set to true in the context, d_queue is emptied - * and not used again in the context. - */ - //context::CDO d_hasSharedTerms; - - - /** - * The generalization of asserting an equality or a disequality. - * If there are shared equalities, this is added to the equality engine. - * Otherwise, this is put on a queue until there is a shared term. - */ - //void assertLiteral(bool eq, ArithVar s, TNode reason); /** This sends a shared term to the uninterpreted equality engine. */ - //void addAssertionToEqualityEngine(bool eq, ArithVar s, TNode reason); void assertionToEqualityEngine(bool eq, ArithVar s, TNode reason); /** Dequeues the delay queue and asserts these equalities.*/ @@ -210,7 +195,7 @@ private: public: - ArithCongruenceManager(context::Context* satContext, ConstraintDatabase&, TNodeCallBack& setLiteral, const ArithVarNodeMap&, NodeCallBack& raiseConflict); + ArithCongruenceManager(context::Context* satContext, ConstraintDatabase&, SetupLiteralCallBack, const ArithVariables&, RaiseConflict raiseConflict); Node explain(TNode literal); void explain(TNode lit, NodeBuilder<>& out); diff --git a/src/theory/arith/constraint.cpp b/src/theory/arith/constraint.cpp index 28b999852..e26687bf1 100644 --- a/src/theory/arith/constraint.cpp +++ b/src/theory/arith/constraint.cpp @@ -371,8 +371,22 @@ void ConstraintValue::setAssertedToTheTheory(TNode witness) { d_database->pushAssertionOrderWatch(this, witness); } -// bool ConstraintValue::isPseudoConstraint() const { -// return d_proof == d_database->d_pseudoConstraintProof; +bool ConstraintValue::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 ConstraintValue::isPsuedoConstraint() const { +// return d_proof == d_database->d_psuedoConstraintProof; // } bool ConstraintValue::isSelfExplaining() const { @@ -394,7 +408,7 @@ bool ConstraintValue::sanityChecking(Node n) const { TNode left = pleft.getNode(); DeltaRational right = cmp.normalizedDeltaRational(); - const ArithVarNodeMap& av2node = d_database->getArithVarNodeMap(); + const ArithVariables& avariables = d_database->getArithVariables(); Debug("nf::tmp") << cmp.getNode() << endl; Debug("nf::tmp") << k << endl; @@ -402,13 +416,13 @@ bool ConstraintValue::sanityChecking(Node n) const { Debug("nf::tmp") << left << endl; Debug("nf::tmp") << right << endl; Debug("nf::tmp") << getValue() << endl; - Debug("nf::tmp") << av2node.hasArithVar(left) << endl; - Debug("nf::tmp") << av2node.asArithVar(left) << endl; + Debug("nf::tmp") << avariables.hasArithVar(left) << endl; + Debug("nf::tmp") << avariables.asArithVar(left) << endl; Debug("nf::tmp") << getVariable() << endl; - if(av2node.hasArithVar(left) && - av2node.asArithVar(left) == getVariable() && + if(avariables.hasArithVar(left) && + avariables.asArithVar(left) == getVariable() && getValue() == right){ switch(getType()){ case LowerBound: @@ -469,12 +483,12 @@ Constraint ConstraintValue::makeNegation(ArithVar v, ConstraintType t, const Del } } -ConstraintDatabase::ConstraintDatabase(context::Context* satContext, context::Context* userContext, const ArithVarNodeMap& av2nodeMap, ArithCongruenceManager& cm, NodeCallBack& raiseConflict) +ConstraintDatabase::ConstraintDatabase(context::Context* satContext, context::Context* userContext, const ArithVariables& avars, ArithCongruenceManager& cm, RaiseConflict raiseConflict) : d_varDatabases(), d_toPropagate(satContext), d_proofs(satContext, false), d_watches(new Watches(satContext, userContext)), - d_av2nodeMap(av2nodeMap), + d_avariables(avars), d_congruenceManager(cm), d_satContext(satContext), d_satAllocationLevel(d_satContext->getLevel()), @@ -670,7 +684,7 @@ Constraint ConstraintDatabase::addLiteral(TNode literal){ Polynomial nvp = posCmp.normalizedVariablePart(); Debug("nf::tmp") << "here " << nvp.getNode() << " " << endl; - ArithVar v = d_av2nodeMap.asArithVar(nvp.getNode()); + ArithVar v = d_avariables.asArithVar(nvp.getNode()); DeltaRational posDR = posCmp.normalizedDeltaRational(); diff --git a/src/theory/arith/constraint.h b/src/theory/arith/constraint.h index e1c55f113..a5d64a652 100644 --- a/src/theory/arith/constraint.h +++ b/src/theory/arith/constraint.h @@ -72,12 +72,10 @@ #include "context/cdqueue.h" #include "theory/arith/arithvar.h" -#include "theory/arith/arithvar_node_map.h" #include "theory/arith/delta_rational.h" - #include "theory/arith/congruence_manager.h" - #include "theory/arith/constraint_forward.h" +#include "theory/arith/callbacks.h" #include #include @@ -604,6 +602,8 @@ public: /** The node must have a proof already and be eligible for propagation! */ void propagate(); + bool satisfiedBy(const DeltaRational& dr) const; + private: /** * Marks the node as having a proof and being selfExplaining. @@ -620,11 +620,6 @@ private: void markAsTrue(Constraint a, Constraint b); void markAsTrue(const std::vector& b); -public: - - -private: - void debugPrint() const; /** @@ -776,20 +771,18 @@ private: static bool emptyDatabase(const std::vector& vec); /** Map from nodes to arithvars. */ - const ArithVarNodeMap& d_av2nodeMap; + const ArithVariables& d_avariables; - const ArithVarNodeMap& getArithVarNodeMap() const{ - return d_av2nodeMap; + const ArithVariables& getArithVariables() const{ + return d_avariables; } ArithCongruenceManager& d_congruenceManager; - //Constraint allocateConstraintForLiteral(ArithVar v, Node literal); - const context::Context * const d_satContext; const int d_satAllocationLevel; - NodeCallBack& d_raiseConflict; + RaiseConflict d_raiseConflict; friend class ConstraintValue; @@ -797,9 +790,9 @@ public: ConstraintDatabase( context::Context* satContext, context::Context* userContext, - const ArithVarNodeMap& av2nodeMap, + const ArithVariables& variables, ArithCongruenceManager& dm, - NodeCallBack& conflictCallBack); + RaiseConflict conflictCallBack); ~ConstraintDatabase(); diff --git a/src/theory/arith/delta_rational.h b/src/theory/arith/delta_rational.h index e97bde555..7945b44c3 100644 --- a/src/theory/arith/delta_rational.h +++ b/src/theory/arith/delta_rational.h @@ -160,6 +160,14 @@ public: } + DeltaRational abs() const { + if(sgn() >= 0){ + return *this; + }else{ + return (*this) * Rational(-1); + } + } + bool operator==(const DeltaRational& other) const{ return (k == other.k) && (c == other.c); } @@ -196,13 +204,20 @@ public: return *(this); } - DeltaRational& operator+=(DeltaRational& other){ + DeltaRational& operator+=(const DeltaRational& other){ c += other.c; k += other.k; return *(this); } + DeltaRational& operator/=(const Rational& a){ + Assert(!a.isZero()); + c /= a; + k /= a; + return *(this); + } + bool isIntegral() const { if(infinitesimalIsZero()){ return getNoninfinitesimalPart().isIntegral(); @@ -260,6 +275,17 @@ public: */ static void seperatingDelta(Rational& res, const DeltaRational& a, const DeltaRational& b); + uint32_t complexity() const { + return c.complexity() + k.complexity(); + } + + double approx(double deltaSub) const { + double maj = getNoninfinitesimalPart().getDouble(); + double min = deltaSub * (getInfinitesimalPart().getDouble()); + return maj + min; + } + + }; std::ostream& operator<<(std::ostream& os, const DeltaRational& n); diff --git a/src/theory/arith/dio_solver.cpp b/src/theory/arith/dio_solver.cpp index a0a932bb4..92cd50864 100644 --- a/src/theory/arith/dio_solver.cpp +++ b/src/theory/arith/dio_solver.cpp @@ -15,6 +15,7 @@ **/ #include "theory/arith/dio_solver.h" +#include "theory/arith/options.h" #include @@ -30,7 +31,7 @@ inline Node makeIntegerVariable(){ } DioSolver::DioSolver(context::Context* ctxt) : - d_lastUsedVariable(ctxt,0), + d_lastUsedProofVariable(ctxt,0), d_inputConstraints(ctxt), d_nextInputConstraintToEnqueue(ctxt, 0), d_trail(ctxt), @@ -42,7 +43,8 @@ DioSolver::DioSolver(context::Context* ctxt) : d_maxInputCoefficientLength(ctxt, 0), d_usedDecomposeIndex(ctxt, false), d_lastPureSubstitution(ctxt, 0), - d_pureSubstitionIter(ctxt, 0) + d_pureSubstitionIter(ctxt, 0), + d_decompositionLemmaQueue(ctxt) {} DioSolver::Statistics::Statistics() : @@ -90,34 +92,34 @@ bool DioSolver::queueConditions(TrailIndex t){ !triviallyUnsat(t); } -size_t DioSolver::allocateVariableInPool() { - Assert(d_lastUsedVariable <= d_variablePool.size()); - if(d_lastUsedVariable == d_variablePool.size()){ - Assert(d_lastUsedVariable == d_variablePool.size()); +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_variablePool.push_back(Variable(intVar)); + d_proofVariablePool.push_back(Variable(intVar)); } - size_t res = d_lastUsedVariable; - d_lastUsedVariable = d_lastUsedVariable + 1; + 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; +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; + 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; - } + 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){ @@ -144,8 +146,9 @@ void DioSolver::pushInputConstraint(const Comparison& eq, Node reason){ d_maxInputCoefficientLength = length; } - size_t varIndex = allocateVariableInPool(); - Variable proofVariable(d_variablePool[varIndex]); + size_t varIndex = allocateProofVariable(); + Variable proofVariable(d_proofVariablePool[varIndex]); + //Variable proofVariable(makeIntegerVariable()); TrailIndex posInTrail = d_trail.size(); d_trail.push_back(Constraint(sp,Polynomial(Monomial(VarList(proofVariable))))); @@ -429,7 +432,7 @@ bool DioSolver::processEquations(bool allowDecomposition){ reduceIndex = minimum; }else{ TrailIndex implied = impliedGcdOfOne(); - + if(implied != 0){ p = solveIndex(implied); reduceIndex = implied; @@ -466,7 +469,7 @@ Node DioSolver::processEquationsForConflict(){ ++(d_statistics.d_conflictCalls); Assert(!inConflict()); - if(processEquations(false)){ + if(processEquations(true)){ ++(d_statistics.d_conflicts); return proveIndex(getConflictIndex()); }else{ @@ -604,7 +607,7 @@ std::pair DioSolver::solveIndex(DioS Assert(p.isIntegral()); Assert(p.selectAbsMinimum() == d_trail[i].d_minimalMonomial); - const Monomial& av = d_trail[i].d_minimalMonomial; + const Monomial av = d_trail[i].d_minimalMonomial; VarList vl = av.getVarList(); Assert(vl.singleton()); @@ -648,7 +651,7 @@ std::pair DioSolver::decomposeIndex( Integer a_abs = a.getValue().getNumerator().abs(); Assert(a_abs > 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()); @@ -673,6 +676,7 @@ std::pair DioSolver::decomposeIndex( TrailIndex ci = d_trail.size(); d_trail.push_back(Constraint(newSI, Polynomial::mkZero())); // no longer reference av safely! + addTrailElementAsLemma(ci); Debug("arith::dio") << "Decompose ci(" << ci <<":" << d_trail[ci].d_eq.getNode() << ") for " << d_trail[i].d_minimalMonomial.getNode() << endl; @@ -796,6 +800,19 @@ void DioSolver::subAndReduceCurrentFByIndex(DioSolver::SubIndex subIndex){ } } +void DioSolver::addTrailElementAsLemma(TrailIndex i) { + if(options::exportDioDecompositions()){ + d_decompositionLemmaQueue.push(i); + } +} + +Node DioSolver::trailIndexToEquality(TrailIndex i) const { + const SumPair& sp = d_trail[i].d_eq; + Node zero = mkRationalNode(0); + Node eq = (sp.getNode()).eqNode(zero); + return eq; +} + }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/arith/dio_solver.h b/src/theory/arith/dio_solver.h index b940bce76..f19291c98 100644 --- a/src/theory/arith/dio_solver.h +++ b/src/theory/arith/dio_solver.h @@ -20,10 +20,13 @@ #define __CVC4__THEORY__ARITH__DIO_SOLVER_H #include "context/context.h" +#include "context/cdo.h" +#include "context/cdlist.h" +#include "context/cdqueue.h" -#include "theory/arith/matrix.h" #include "theory/arith/partial_model.h" #include "util/rational.h" +#include "theory/arith/normal_form.h" #include "util/statistics_registry.h" @@ -40,8 +43,9 @@ private: typedef size_t InputConstraintIndex; typedef size_t SubIndex; - std::vector d_variablePool; - context::CDO d_lastUsedVariable; + std::vector d_proofVariablePool; + /** Sat context dependent. */ + context::CDO d_lastUsedProofVariable; /** * The set of input constraints is stored in a CDList. @@ -164,6 +168,11 @@ private: 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. */ @@ -231,11 +240,11 @@ private: } /** - * Allocates a "unique" variables from the pool of integer variables. + * Allocates a "unique" proof variable. * This variable is fresh with respect to the context. * Returns index of the variable in d_variablePool; */ - size_t allocateVariableInPool(); + size_t allocateProofVariable(); /** Empties the unproccessed input constraints into the queue. */ @@ -376,6 +385,21 @@ private: void debugPrintTrail(TrailIndex i) const; 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: diff --git a/src/theory/arith/dual_simplex.cpp b/src/theory/arith/dual_simplex.cpp new file mode 100644 index 000000000..7caee6708 --- /dev/null +++ b/src/theory/arith/dual_simplex.cpp @@ -0,0 +1,259 @@ +/********************* */ +/*! \file simplex.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/dual_simplex.h" +#include "theory/arith/options.h" +#include "theory/arith/constraint.h" + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + +DualSimplexDecisionProcedure::DualSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(linEq, errors, conflictChannel, tvmalloc) + , d_pivotsInRound() + , d_statistics(d_pivots) +{ } + +DualSimplexDecisionProcedure::Statistics::Statistics(uint32_t& pivots): + d_statUpdateConflicts("theory::arith::dual::UpdateConflicts", 0), + d_processSignalsTime("theory::arith::dual::findConflictOnTheQueueTime"), + d_simplexConflicts("theory::arith::dual::simplexConflicts",0), + d_recentViolationCatches("theory::arith::dual::recentViolationCatches",0), + d_searchTime("theory::arith::dual::searchTime"), + d_finalCheckPivotCounter("theory::arith::dual::lastPivots", pivots) +{ + StatisticsRegistry::registerStat(&d_statUpdateConflicts); + StatisticsRegistry::registerStat(&d_processSignalsTime); + StatisticsRegistry::registerStat(&d_simplexConflicts); + StatisticsRegistry::registerStat(&d_recentViolationCatches); + StatisticsRegistry::registerStat(&d_searchTime); + StatisticsRegistry::registerStat(&d_finalCheckPivotCounter); +} + +DualSimplexDecisionProcedure::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_statUpdateConflicts); + StatisticsRegistry::unregisterStat(&d_processSignalsTime); + StatisticsRegistry::unregisterStat(&d_simplexConflicts); + StatisticsRegistry::unregisterStat(&d_recentViolationCatches); + StatisticsRegistry::unregisterStat(&d_searchTime); + StatisticsRegistry::unregisterStat(&d_finalCheckPivotCounter); +} + +Result::Sat DualSimplexDecisionProcedure::dualFindModel(bool exactResult){ + Assert(d_conflictVariables.empty()); + + static CVC4_THREADLOCAL(unsigned int) instance = 0; + instance = instance + 1; + d_pivots = 0; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Debug("arith::findModel") << "dualFindModel("<< instance <<") trivial" << endl; + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + d_errorSet.setSelectionRule(VAR_ORDER); + + if(processSignals()){ + d_conflictVariables.purge(); + + Debug("arith::findModel") << "dualFindModel("<< instance <<") early conflict" << endl; + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Debug("arith::findModel") << "dualFindModel("<< instance <<") fixed itself" << endl; + Assert(!d_errorSet.moreSignals()); + return Result::SAT; + } + + Debug("arith::findModel") << "dualFindModel(" << instance <<") start non-trivial" << endl; + + Result::Sat result = Result::SAT_UNKNOWN; + + static const bool verbose = false; + exactResult |= options::arithStandardCheckVarOrderPivots() < 0; + + + uint32_t checkPeriod = options::arithSimplexCheckPeriod(); + if(result == Result::SAT_UNKNOWN){ + uint32_t numDifferencePivots = options::arithHeuristicPivots() < 0 ? + d_numVariables + 1 : options::arithHeuristicPivots(); + // The signed to unsigned conversion is safe. + if(numDifferencePivots > 0){ + + d_errorSet.setSelectionRule(d_heuristicRule); + if(searchForFeasibleSolution(numDifferencePivots)){ + result = Result::UNSAT; + } + } + + if(verbose && numDifferencePivots > 0){ + if(result == Result::UNSAT){ + Message() << "diff order found unsat" << endl; + }else if(d_errorSet.errorEmpty()){ + Message() << "diff order found model" << endl; + }else{ + Message() << "diff order missed" << endl; + } + } + } + Assert(!d_errorSet.moreSignals()); + + if(!d_errorSet.errorEmpty() && result != Result::UNSAT){ + if(exactResult){ + d_errorSet.setSelectionRule(VAR_ORDER); + while(!d_errorSet.errorEmpty() && result != Result::UNSAT){ + Assert(checkPeriod > 0); + if(searchForFeasibleSolution(checkPeriod)){ + result = Result::UNSAT; + } + } + }else if( options::arithStandardCheckVarOrderPivots() > 0){ + d_errorSet.setSelectionRule(VAR_ORDER); + if(searchForFeasibleSolution(options::arithStandardCheckVarOrderPivots())){ + result = Result::UNSAT; + } + if(verbose){ + if(result == Result::UNSAT){ + Message() << "restricted var order found unsat" << endl; + }else if(d_errorSet.errorEmpty()){ + Message() << "restricted var order found model" << endl; + }else{ + Message() << "restricted var order missed" << endl; + } + } + } + } + + Assert(!d_errorSet.moreSignals()); + if(result == Result::SAT_UNKNOWN && d_errorSet.errorEmpty()){ + result = Result::SAT; + } + + d_pivotsInRound.purge(); + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Debug("arith::findModel") << "end findModel() " << instance << " " << result << endl; + + return result; +} + +//corresponds to Check() in dM06 +//template +bool DualSimplexDecisionProcedure::searchForFeasibleSolution(uint32_t remainingIterations){ + TimerStat::CodeTimer codeTimer(d_statistics.d_searchTime); + + Debug("arith") << "searchForFeasibleSolution" << endl; + Assert(remainingIterations > 0); + + while(remainingIterations > 0 && !d_errorSet.focusEmpty()){ + if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + Assert(d_conflictVariables.empty()); + ArithVar x_i = d_errorSet.topFocusVariable(); + + Debug("arith::update::select") << "selectSmallestInconsistentVar()=" << x_i << endl; + if(x_i == ARITHVAR_SENTINEL){ + Debug("arith::update") << "No inconsistent variables" << endl; + return false; //sat + } + + --remainingIterations; + + bool useVarOrderPivot = d_pivotsInRound.count(x_i) >= options::arithPivotThreshold(); + if(!useVarOrderPivot){ + d_pivotsInRound.add(x_i); + } + + + Debug("arith::update") + << "pivots in rounds: " << d_pivotsInRound.count(x_i) + << " use " << useVarOrderPivot + << " threshold " << options::arithPivotThreshold() + << 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 = 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 = d_errorSet.errorSize(); + d_pivots++; + + // cout << "#" << 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; +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/dual_simplex.h b/src/theory/arith/dual_simplex.h new file mode 100644 index 000000000..0ec4bc238 --- /dev/null +++ b/src/theory/arith/dual_simplex.h @@ -0,0 +1,115 @@ +/********************* */ +/*! \file simplex.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#pragma once + +#include "util/statistics_registry.h" +#include "theory/arith/simplex.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +class DualSimplexDecisionProcedure : public SimplexDecisionProcedure{ +public: + DualSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc); + + Result::Sat findModel(bool exactResult) { + 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::Sat 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); + ~Statistics(); + } d_statistics; +};/* class DualSimplexDecisionProcedure */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/error_set.cpp b/src/theory/arith/error_set.cpp new file mode 100644 index 000000000..ee72d1949 --- /dev/null +++ b/src/theory/arith/error_set.cpp @@ -0,0 +1,493 @@ +/********************* */ +/*! \file arith_priority_queue.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: mdeters + ** Minor contributors (to current version): none + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/error_set.h" +#include "theory/arith/constraint.h" + +using namespace std; + +namespace CVC4 { +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(NULL) + , d_metric(0) +{ + Debug("arith::error::mem") << "def constructor " << d_variable << " " << d_amount << endl; +} + +ErrorInformation::ErrorInformation(ArithVar var, Constraint vio, int sgn) + : d_variable(var) + , d_violated(vio) + , d_sgn(sgn) + , d_relaxed(false) + , d_inFocus(false) + , d_handle() + , d_amount(NULL) + , d_metric(0) +{ + Assert(debugInitialized()); + Debug("arith::error::mem") << "constructor " << d_variable << " " << d_amount << endl; +} + + +ErrorInformation::~ErrorInformation() { + Assert(d_relaxed != true); + if(d_amount != NULL){ + Debug("arith::error::mem") << d_amount << endl; + Debug("arith::error::mem") << "destroy " << d_variable << " " << d_amount << endl; + delete d_amount; + d_amount = NULL; + } +} + +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 == NULL){ + d_amount = NULL; + }else{ + d_amount = new DeltaRational(*ei.d_amount); + } + Debug("arith::error::mem") << "copy const " << d_variable << " " << d_amount << 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 != NULL && ei.d_amount != NULL){ + Debug("arith::error::mem") << "assignment assign " << d_variable << " " << d_amount << endl; + *d_amount = *ei.d_amount; + }else if(ei.d_amount != NULL){ + d_amount = new DeltaRational(*ei.d_amount); + Debug("arith::error::mem") << "assignment alloc " << d_variable << " " << d_amount << endl; + }else if(d_amount != NULL){ + Debug("arith::error::mem") << "assignment release " << d_variable << " " << d_amount << endl; + delete d_amount; + d_amount = NULL; + }else{ + d_amount = NULL; + } + return *this; +} + +void ErrorInformation::reset(Constraint c, int sgn){ + Assert(!isRelaxed()); + Assert(c != NullConstraint); + d_violated = c; + d_sgn = sgn; + + if(d_amount != NULL){ + delete d_amount; + Debug("arith::error::mem") << "reset " << d_variable << " " << d_amount << endl; + d_amount = NULL; + } +} + +void ErrorInformation::setAmount(const DeltaRational& am){ + if(d_amount == NULL){ + d_amount = new DeltaRational; + Debug("arith::error::mem") << "setAmount " << d_variable << " " << d_amount << endl; + } + (*d_amount) = am; +} + +ErrorSet::Statistics::Statistics(): + d_enqueues("theory::arith::pqueue::enqueues", 0), + d_enqueuesCollection("theory::arith::pqueue::enqueuesCollection", 0), + d_enqueuesDiffMode("theory::arith::pqueue::enqueuesDiffMode", 0), + d_enqueuesVarOrderMode("theory::arith::pqueue::enqueuesVarOrderMode", 0), + d_enqueuesCollectionDuplicates("theory::arith::pqueue::enqueuesCollectionDuplicates", 0), + d_enqueuesVarOrderModeDuplicates("theory::arith::pqueue::enqueuesVarOrderModeDuplicates", 0) +{ + StatisticsRegistry::registerStat(&d_enqueues); + StatisticsRegistry::registerStat(&d_enqueuesCollection); + StatisticsRegistry::registerStat(&d_enqueuesDiffMode); + StatisticsRegistry::registerStat(&d_enqueuesVarOrderMode); + StatisticsRegistry::registerStat(&d_enqueuesCollectionDuplicates); + StatisticsRegistry::registerStat(&d_enqueuesVarOrderModeDuplicates); +} + +ErrorSet::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_enqueues); + StatisticsRegistry::unregisterStat(&d_enqueuesCollection); + StatisticsRegistry::unregisterStat(&d_enqueuesDiffMode); + StatisticsRegistry::unregisterStat(&d_enqueuesVarOrderMode); + StatisticsRegistry::unregisterStat(&d_enqueuesCollectionDuplicates); + StatisticsRegistry::unregisterStat(&d_enqueuesVarOrderModeDuplicates); +} + +ErrorSet::ErrorSet(ArithVariables& vars, TableauSizes tabSizes, BoundCountingLookup lookups): + d_variables(vars), + d_errInfo(), + d_selectionRule(VAR_ORDER), + d_focus(ComparatorPivotRule(this,d_selectionRule)), + d_outOfFocus(), + d_signals(), + d_tableauSizes(tabSizes), + d_boundLookup(lookups) +{} + +ErrorSelectionRule ErrorSet::getSelectionRule() const{ + return d_selectionRule; +} + +void ErrorSet::recomputeAmount(ErrorInformation& ei, ErrorSelectionRule rule){ + switch(rule){ + case MINIMUM_AMOUNT: + case MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(ei.getVariable())); + break; + case SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + break; + case VAR_ORDER: + //do nothing + break; + } +} + +void ErrorSet::setSelectionRule(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, ErrorSelectionRule r): + d_errorSet(es), d_rule (r) +{} + +bool ComparatorPivotRule::operator()(ArithVar v, ArithVar u) const { + switch(d_rule){ + case VAR_ORDER: + // This needs to be the reverse of the minVariableOrder + return v > u; + case 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 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 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 MINIMUM_AMOUNT: + case MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(ei.getVariable())); + d_focus.modify(ei.getHandle(), ei.getVariable()); + break; + case SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + d_focus.modify(ei.getHandle(), ei.getVariable()); + break; + case 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()){ + Constraint 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; + Constraint 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 MINIMUM_AMOUNT: + case MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(v)); + break; + case SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + break; + case 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 MINIMUM_AMOUNT: + case MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(v)); + break; + case SUM_METRIC: + ei.setMetric(sumMetric(v)); + break; + case 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){ + Constraint 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; +} + +ostream& operator<<(ostream& out, ErrorSelectionRule rule) { + switch(rule) { + case VAR_ORDER: + out << "VAR_ORDER"; + break; + case MINIMUM_AMOUNT: + out << "MINIMUM_AMOUNT"; + break; + case MAXIMUM_AMOUNT: + out << "MAXIMUM_AMOUNT"; + break; + case SUM_METRIC: + out << "SUM_METRIC"; + break; + } + + return out; +} + +void ErrorSet::debugPrint(std::ostream& out) const { + static int instance = 0; + ++instance; + out << "error set debugprint " << instance << 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); + } +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/error_set.h b/src/theory/arith/error_set.h new file mode 100644 index 000000000..ce6412573 --- /dev/null +++ b/src/theory/arith/error_set.h @@ -0,0 +1,407 @@ +/********************* */ +/*! \file arith_priority_queue.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "cvc4_private.h" + +#pragma once + +#include "theory/arith/arithvar.h" +#include "theory/arith/bound_counts.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/arith_heuristic_pivot_rule.h" +#include "theory/arith/tableau_sizes.h" + +#include "util/statistics_registry.h" +//#include +#include + +#include + +namespace CVC4 { +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 ErrorInfoMap; + +class ComparatorPivotRule { +private: + const ErrorSet* d_errorSet; + + ErrorSelectionRule d_rule; +public: + ComparatorPivotRule(); + ComparatorPivotRule(const ErrorSet* es, ErrorSelectionRule r); + + bool operator()(ArithVar v, ArithVar u) const; + 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 __gnu_pbds::priority_queue< + ArithVar, + ComparatorPivotRule, + __gnu_pbds::pairing_heap_tag> FocusSet; + +typedef FocusSet::point_iterator 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 + */ + Constraint 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. + */ + DeltaRational* d_amount; + + /** */ + uint32_t d_metric; + +public: + ErrorInformation(); + ErrorInformation(ArithVar var, Constraint vio, int sgn); + ~ErrorInformation(); + ErrorInformation(const ErrorInformation& ei); + ErrorInformation& operator=(const ErrorInformation& ei); + + void reset(Constraint 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 != NULL); + 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 Constraint 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 == NULL){ + os << "NULL"; + }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; + + 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, 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; + + ErrorSelectionRule getSelectionRule() const; + void setSelectionRule(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.boundCounts(a); + uint32_t count = getSgn(a) > 0 ? bcs.atUpperBounds() : bcs.atLowerBounds(); + + uint32_t length = d_tableauSizes.getRowLength(a); + + return (length - count); + } + + uint32_t getMetric(ArithVar a) const { + return d_errInfo[a].getMetric(); + } + + Constraint 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(); + }; + + Statistics d_statistics; +}; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/fc_simplex.cpp b/src/theory/arith/fc_simplex.cpp new file mode 100644 index 000000000..ac4625ba3 --- /dev/null +++ b/src/theory/arith/fc_simplex.cpp @@ -0,0 +1,853 @@ +/********************* */ +/*! \file simplex.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/fc_simplex.h" +#include "theory/arith/options.h" +#include "theory/arith/constraint.h" + +#include "util/statistics_registry.h" + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + + +FCSimplexDecisionProcedure::FCSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(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(d_pivots) +{ } + +FCSimplexDecisionProcedure::Statistics::Statistics(uint32_t& pivots): + d_initialSignalsTime("theory::arith::FC::initialProcessTime"), + d_initialConflicts("theory::arith::FC::UpdateConflicts", 0), + d_fcFoundUnsat("theory::arith::FC::FoundUnsat", 0), + d_fcFoundSat("theory::arith::FC::FoundSat", 0), + d_fcMissed("theory::arith::FC::Missed", 0), + d_fcTimer("theory::arith::FC::Timer"), + d_fcFocusConstructionTimer("theory::arith::FC::Construction"), + d_selectUpdateForDualLike("theory::arith::FC::selectUpdateForDualLike"), + d_selectUpdateForPrimal("theory::arith::FC::selectUpdateForPrimal"), + d_finalCheckPivotCounter("theory::arith::FC::lastPivots", pivots) +{ + StatisticsRegistry::registerStat(&d_initialSignalsTime); + StatisticsRegistry::registerStat(&d_initialConflicts); + + StatisticsRegistry::registerStat(&d_fcFoundUnsat); + StatisticsRegistry::registerStat(&d_fcFoundSat); + StatisticsRegistry::registerStat(&d_fcMissed); + + StatisticsRegistry::registerStat(&d_fcTimer); + StatisticsRegistry::registerStat(&d_fcFocusConstructionTimer); + + StatisticsRegistry::registerStat(&d_selectUpdateForDualLike); + StatisticsRegistry::registerStat(&d_selectUpdateForPrimal); + + StatisticsRegistry::registerStat(&d_finalCheckPivotCounter); +} + +FCSimplexDecisionProcedure::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_initialSignalsTime); + StatisticsRegistry::unregisterStat(&d_initialConflicts); + + StatisticsRegistry::unregisterStat(&d_fcFoundUnsat); + StatisticsRegistry::unregisterStat(&d_fcFoundSat); + StatisticsRegistry::unregisterStat(&d_fcMissed); + + StatisticsRegistry::unregisterStat(&d_fcTimer); + StatisticsRegistry::unregisterStat(&d_fcFocusConstructionTimer); + + StatisticsRegistry::unregisterStat(&d_selectUpdateForDualLike); + StatisticsRegistry::unregisterStat(&d_selectUpdateForPrimal); + + StatisticsRegistry::unregisterStat(&d_finalCheckPivotCounter); +} + +Result::Sat FCSimplexDecisionProcedure::findModel(bool exactResult){ + Assert(d_conflictVariables.empty()); + Assert(d_sgnDisagreements.empty()); + + d_pivots = 0; + static CVC4_THREADLOCAL(unsigned int) instance = 0; + instance = instance + 1; + static const bool verbose = false; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Debug("arith::findModel") << "fcFindModel("<< instance <<") trivial" << endl; + Assert(d_conflictVariables.empty()); + //if(verbose){ Message() << "fcFindModel("<< instance <<") trivial" << endl; } + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + + // We must start tracking NOW + d_errorSet.setSelectionRule(SUM_METRIC); + + + if(initialProcessSignals()){ + d_conflictVariables.purge(); + if(verbose){ Message() << "fcFindModel("<< instance <<") early conflict" << endl; } + Debug("arith::findModel") << "fcFindModel("<< instance <<") early conflict" << endl; + Assert(d_conflictVariables.empty()); + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + //if(verbose){ Message() << "fcFindModel("<< instance <<") fixed itself" << endl; } + Debug("arith::findModel") << "fcFindModel("<< instance <<") fixed itself" << endl; + if(verbose) + Assert(!d_errorSet.moreSignals()); + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + Debug("arith::findModel") << "fcFindModel(" << instance <<") start non-trivial" << endl; + + exactResult |= options::arithStandardCheckVarOrderPivots() < 0; + + d_prevWitnessImprovement = HeuristicDegenerate; + d_witnessImprovementInARow = 0; + + Result::Sat result = Result::SAT_UNKNOWN; + + if(result == Result::SAT_UNKNOWN){ + if(exactResult){ + d_pivotBudget = -1; + }else{ + d_pivotBudget = options::arithStandardCheckVarOrderPivots(); + } + + result = dualLike(); + + if(result == Result::UNSAT){ + ++(d_statistics.d_fcFoundUnsat); + if(verbose){ Message() << "fc found unsat";} + }else if(d_errorSet.errorEmpty()){ + ++(d_statistics.d_fcFoundSat); + if(verbose){ Message() << "fc found model"; } + }else{ + ++(d_statistics.d_fcMissed); + if(verbose){ Message() << "fc missed"; } + } + } + if(verbose){ + Message() << "(" << instance << ") pivots " << d_pivots << endl; + } + + Assert(!d_errorSet.moreSignals()); + if(result == Result::SAT_UNKNOWN && d_errorSet.errorEmpty()){ + result = Result::SAT; + } + + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Debug("arith::findModel") << "end findModel() " << instance << " " << 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(); + } + + Debug("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; + + static int instance = 0 ; + ++instance; + + Debug("arith::selectPrimalUpdate") + << "selectPrimalUpdate " << instance << endl + << basic << " " << d_tableau.basicRowLength(basic) + << " " << d_linEq._countBounds(basic) << endl; + + static const 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); + + Debug("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()){ + Debug("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); + 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; + +#warning "Who is using computeSafeUpdate?" + LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); + UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); + + //int curr_movement = cand.d_sgn; + // if(isFocus){ + // currProposal = d_linEq.speculativeUpdate(curr, coeff, upf); + // }else{ + // currProposal = UpdateInfo(curr, curr_movement); + // d_linEq.computeSafeUpdate(currProposal, bpf); + // } + + Debug("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); + Debug("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; + Debug("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{ + Debug("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(Debug.isOn("arith::focus")){ + d_errorSet.debugPrint(Debug("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()); + Debug("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(); + Debug("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); + Debug("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{ + Debug("updateAndSignal") << "updated basic " << updated; + Debug("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); + Debug("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); + int dir = !d_variables.assignmentIsConsistent(updated) ? + d_errorSet.getSgn(updated) : 0; + Debug("updateAndSignal") << " dir " << dir; + Debug("updateAndSignal") << " _countBounds " << d_linEq._countBounds(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(); + + static bool verbose = false; + + Debug("updateAndSignal") << "updateAndSignal " << selected << endl; + + stringstream ss; + if(verbose){ + d_errorSet.debugPrint(ss); + if(selected.describesPivot()){ + ArithVar leaving = selected.leaving(); + ss << "leaving " << leaving + << " " << d_tableau.basicRowLength(leaving) + << " " << d_linEq._countBounds(leaving) + << endl; + } + if(degenerate(w) && selected.describesPivot()){ + ArithVar leaving = selected.leaving(); + Message() + << "degenerate " << leaving + << ", atBounds " << d_linEq.basicsAtBounds(selected) + << ", len " << d_tableau.basicRowLength(leaving) + << ", bc " << d_linEq._countBounds(leaving) + << endl; + } + } + + if(selected.describesPivot()){ + Constraint 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(Debug.isOn("updateAndSignal")){debugPrintSignal(updated);} + if(!d_variables.assignmentIsConsistent(updated)){ + if(checkBasicForConflict(updated)){ + reportConflict(updated); + Assert(debugUpdatedBasic(selected, updated)); + } + } + }else{ + Debug("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(verbose){ + Message() << "conflict variable " << selected << endl; + Message() << ss.str(); + } + if(Debug.isOn("error")){ d_errorSet.debugPrint(Debug("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){ + + Debug("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); + + Debug("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); + Debug("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()){ + Debug("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)){ + Debug("selectFocusImproving") << "only degenerate" << endl; + if(d_prevWitnessImprovement == HeuristicDegenerate && + d_witnessImprovementInARow >= s_focusThreshold){ + Debug("selectFocusImproving") << "focus down been degenerate too long" << endl; + return focusDownToLastHalf(); + }else{ + Debug("selectFocusImproving") << "taking degenerate" << endl; + } + } + Debug("selectFocusImproving") << "selectFocusImproving did this " << selected << endl; + + updateAndSignal(selected, w); + logPivot(w); + return w; +} + +bool FCSimplexDecisionProcedure::debugDualLike(WitnessImprovement w, ostream& out, int instance, uint32_t prevFocusSize, uint32_t prevErrorSize ) const{ + out << "DLV("< 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::Sat FCSimplexDecisionProcedure::dualLike(){ + static int instance = 0; + static bool verbose = false; + + 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()){ + ++instance; + Debug("dualLike") << "dualLike " << instance << 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); + + Debug("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(); + Debug("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 const unsigned s_sumMetricThreshold = 1; + if(d_errorSet.sumMetric(e) <= s_sumMetricThreshold){ + Debug("dualLike") << "dualLikeImproveError " << e << endl; + w = dualLikeImproveError(e); + }else{ + Debug("dualLike") << "selectFocusImproving " << endl; + w = selectFocusImproving(); + } + } + Assert(d_focusSize == d_errorSet.focusSize()); + Assert(d_errorSize == d_errorSet.errorSize()); + + if(verbose){ + debugDualLike(w, Message(), instance, prevFocusSize, prevErrorSize); + } + Assert(debugDualLike(w, Debug("dualLike"), instance, prevFocusSize, prevErrorSize)); + } + + + 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::SAT_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; + } +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/fc_simplex.h b/src/theory/arith/fc_simplex.h new file mode 100644 index 000000000..0dafa83ff --- /dev/null +++ b/src/theory/arith/fc_simplex.h @@ -0,0 +1,252 @@ +/********************* */ +/*! \file simplex.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#pragma once + +#include "theory/arith/simplex.h" +#include "util/dense_map.h" +#include "util/statistics_registry.h" +#include + +namespace CVC4 { +namespace theory { +namespace arith { + +class FCSimplexDecisionProcedure : public SimplexDecisionProcedure{ +public: + FCSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc); + + Result::Sat findModel(bool exactResult); + + // other error variables are dropping + WitnessImprovement dualLikeImproveError(ArithVar evar); + WitnessImprovement primalImproveError(ArithVar evar); + + // dual like + // - found conflict + // - satisfied error set + Result::Sat dualLike(); + +private: + static const 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; + // enum PivotImprovement { + // ErrorDropped, + // NonDegenerate, + // HeuristicDegenerate, + // BlandsDegenerate + // }; + + WitnessImprovement d_prevWitnessImprovement; + uint32_t d_witnessImprovementInARow; + + uint32_t degeneratePivotsInARow() const; + + static const uint32_t s_focusThreshold = 6; + static const uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; + static const 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; + return useBlands ? + &LinearEqualityModule::preferWitness: + &LinearEqualityModule::preferWitness; + } + + bool debugDualLike(WitnessImprovement w, std::ostream& out, + int instance, + uint32_t prevFocusSize, uint32_t prevErrorSize) const; + + void debugPrintSignal(ArithVar updated) const; + + ArithVarVec d_sgnDisagreements; + + //static PivotImprovement pivotImprovement(const UpdateInfo& selected, bool useBlands = false); + + 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 = useBlands ? + &LinearEqualityModule::preferWitness: + &LinearEqualityModule::preferWitness; + + LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? + &LinearEqualityModule::minVarOrder : + &LinearEqualityModule::minRowLength; + bpf = &LinearEqualityModule::minVarOrder; + + 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(uint32_t& pivots); + ~Statistics(); + } d_statistics; +};/* class FCSimplexDecisionProcedure */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/linear_equality.cpp b/src/theory/arith/linear_equality.cpp index 7229eba95..42d8b41f8 100644 --- a/src/theory/arith/linear_equality.cpp +++ b/src/theory/arith/linear_equality.cpp @@ -16,6 +16,7 @@ #include "theory/arith/linear_equality.h" +#include "theory/arith/constraint.h" using namespace std; @@ -23,109 +24,283 @@ namespace CVC4 { namespace theory { namespace arith { -/* Explicitly instatiate this function. */ +/* Explicitly instatiate these functions. */ template void LinearEqualityModule::propagateNonbasics(ArithVar basic, Constraint c); template void LinearEqualityModule::propagateNonbasics(ArithVar basic, Constraint c); +template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; +template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; + +// template bool LinearEqualityModule::preferNonDegenerate(const UpdateInfo& a, const UpdateInfo& b) const; +// template bool LinearEqualityModule::preferNonDegenerate(const UpdateInfo& a, const UpdateInfo& b) const; + +// template bool LinearEqualityModule::preferErrorsFixed(const UpdateInfo& a, const UpdateInfo& b) const; +// template bool LinearEqualityModule::preferErrorsFixed(const UpdateInfo& a, const UpdateInfo& b) 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, BoundCountingVector& boundTracking, BasicVarModelUpdateCallBack f): + d_variables(vars), + d_tableau(t), + d_basicVariableUpdates(f), + d_increasing(1), + d_decreasing(-1), + d_relevantErrorBuffer(), + d_boundTracking(boundTracking), + d_areTracking(false), + d_trackCallback(this) +{} + LinearEqualityModule::Statistics::Statistics(): d_statPivots("theory::arith::pivots",0), d_statUpdates("theory::arith::updates",0), - d_pivotTime("theory::arith::pivotTime") + d_pivotTime("theory::arith::pivotTime"), + d_adjTime("theory::arith::adjTime"), + d_weakeningAttempts("theory::arith::weakening::attempts",0), + d_weakeningSuccesses("theory::arith::weakening::success",0), + d_weakenings("theory::arith::weakening::total",0), + d_weakenTime("theory::arith::weakening::time") { StatisticsRegistry::registerStat(&d_statPivots); StatisticsRegistry::registerStat(&d_statUpdates); StatisticsRegistry::registerStat(&d_pivotTime); + StatisticsRegistry::registerStat(&d_adjTime); + + StatisticsRegistry::registerStat(&d_weakeningAttempts); + StatisticsRegistry::registerStat(&d_weakeningSuccesses); + StatisticsRegistry::registerStat(&d_weakenings); + StatisticsRegistry::registerStat(&d_weakenTime); } LinearEqualityModule::Statistics::~Statistics(){ StatisticsRegistry::unregisterStat(&d_statPivots); StatisticsRegistry::unregisterStat(&d_statUpdates); StatisticsRegistry::unregisterStat(&d_pivotTime); + StatisticsRegistry::unregisterStat(&d_adjTime); + + + StatisticsRegistry::unregisterStat(&d_weakeningAttempts); + StatisticsRegistry::unregisterStat(&d_weakeningSuccesses); + StatisticsRegistry::unregisterStat(&d_weakenings); + StatisticsRegistry::unregisterStat(&d_weakenTime); +} +void LinearEqualityModule::includeBoundCountChange(ArithVar nb, BoundCounts prev){ + if(d_tableau.isBasic(nb)){ + return; + } + Assert(!d_tableau.isBasic(nb)); + Assert(!d_areTracking); + + BoundCounts curr = d_variables.boundCounts(nb); + + Assert(prev != curr); + Tableau::ColIterator basicIter = d_tableau.colIterator(nb); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + Assert(entry.getColVar() == nb); + int a_ijSgn = entry.getCoefficient().sgn(); + + ArithVar basic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + + BoundCounts& counts = d_boundTracking.get(basic); + Debug("includeBoundCountChange") << basic << " " << counts << " to " ; + counts -= prev.multiplyBySgn(a_ijSgn); + counts += curr.multiplyBySgn(a_ijSgn); + Debug("includeBoundCountChange") << counts << " " << a_ijSgn << std::endl; + } + d_boundTracking.set(nb, curr); +} + +void LinearEqualityModule::updateMany(const DenseMap& many){ + for(DenseMap::const_iterator i = many.begin(), i_end = many.end(); i != i_end; ++i){ + ArithVar nb = *i; + 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::update(ArithVar x_i, const DeltaRational& v){ + +void LinearEqualityModule::forceNewBasis(const DenseSet& newBasis){ + 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)); - DeltaRational assignment_x_i = d_partialModel.getAssignment(x_i); + Assert(!d_areTracking); + const DeltaRational& assignment_x_i = d_variables.getAssignment(x_i); ++(d_statistics.d_statUpdates); + Debug("arith") <<"update " << x_i << ": " << assignment_x_i << "|-> " << v << endl; DeltaRational diff = v - assignment_x_i; - //Assert(matchingSets(d_tableau, x_i)); - Tableau::ColIterator basicIter = d_tableau.colIterator(x_i); - for(; !basicIter.atEnd(); ++basicIter){ - const Tableau::Entry& entry = *basicIter; + 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()); - //ReducedRowVector& row_j = d_tableau.lookup(x_j); - - //const Rational& a_ji = row_j.lookup(x_i); const Rational& a_ji = entry.getCoefficient(); - const DeltaRational& assignment = d_partialModel.getAssignment(x_j); + const DeltaRational& assignment = d_variables.getAssignment(x_j); DeltaRational nAssignment = assignment+(diff * a_ji); - d_partialModel.setAssignment(x_j, nAssignment); + d_variables.setAssignment(x_j, nAssignment); d_basicVariableUpdates(x_j); } - d_partialModel.setAssignment(x_i, v); + d_variables.setAssignment(x_i, v); + + if(Debug.isOn("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); + Debug("arith") <<"update " << x_i << ": " + << d_variables.getAssignment(x_i) << "|-> " << v << endl; + + + BoundCounts before = d_variables.boundCounts(x_i); + d_variables.setAssignment(x_i, v); + BoundCounts after = d_variables.boundCounts(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); - //double difference = ((double)d_tableau.getNumRows()) - ((double) d_tableau.getRowLength(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); + Debug("update") << x_j << " " << a_ji << assignment << " -> " << nAssignment << endl; + d_variables.setAssignment(x_j, nAssignment); + + if(anyChange && basicIsTracked(x_j)){ + BoundCounts& next_bc_k = d_boundTracking.get(x_j); + next_bc_k.addInChange(a_ji.sgn(), before, after); + } + + d_basicVariableUpdates(x_j); + } - //(d_statistics.d_avgNumRowsNotContainingOnUpdate).addEntry(difference); if(Debug.isOn("paranoid:check_tableau")){ debugCheckTableau(); } } -void LinearEqualityModule::pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& v){ +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); + static int instance = 0; + + if(Debug.isOn("arith::tracking::pre")){ + ++instance; + Debug("arith::tracking") << "pre update #" << instance << endl; + debugCheckTracking(); + } + if(Debug.isOn("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); - const DeltaRational& betaX_i = d_partialModel.getAssignment(x_i); - - Rational inv_aij = a_ij.inverse(); - DeltaRational theta = (v - betaX_i)*inv_aij; - - d_partialModel.setAssignment(x_i, v); - - - DeltaRational tmp = d_partialModel.getAssignment(x_j) + theta; - d_partialModel.setAssignment(x_j, tmp); - - - //Assert(matchingSets(d_tableau, x_j)); - for(Tableau::ColIterator iter = d_tableau.colIterator(x_j); !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - Assert(entry.getColVar() == x_j); - RowIndex currRow = entry.getRowIndex(); - if(ridx != currRow ){ - ArithVar x_k = d_tableau.rowIndexToBasic(currRow); - const Rational& a_kj = entry.getCoefficient(); - DeltaRational nextAssignment = d_partialModel.getAssignment(x_k) + (theta * a_kj); - d_partialModel.setAssignment(x_k, nextAssignment); - - d_basicVariableUpdates(x_k); - } + if(Debug.isOn("arith::tracking::mid")){ + Debug("arith::tracking") << "postupdate prepivot #" << instance << endl; + debugCheckTracking(); } // Pivots ++(d_statistics.d_statPivots); - d_tableau.pivot(x_i, x_j); + d_tableau.pivot(x_i, x_j, d_trackCallback); + + if(Debug.isOn("arith::tracking::post")){ + Debug("arith::tracking") << "postpivot #" << instance << endl; + debugCheckTracking(); + } d_basicVariableUpdates(x_j); @@ -134,6 +309,51 @@ void LinearEqualityModule::pivotAndUpdate(ArithVar x_i, ArithVar x_j, const Delt } } +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; + Debug("arith::tracking") << "arith::tracking row basic: " << basic << endl; + + for(Tableau::RowIterator iter = d_tableau.basicRowIterator(basic); !iter.atEnd() && Debug.isOn("arith::tracking"); ++iter){ + const Tableau::Entry& entry = *iter; + + ArithVar var = entry.getColVar(); + const Rational& coeff = entry.getCoefficient(); + DeltaRational beta = d_variables.getAssignment(var); + Debug("arith::tracking") << var << " " << d_variables.boundCounts(var) + << " " << beta << coeff; + if(d_variables.hasLowerBound(var)){ + Debug("arith::tracking") << "(lb " << d_variables.getLowerBound(var) << ")"; + } + if(d_variables.hasUpperBound(var)){ + Debug("arith::tracking") << "(up " << d_variables.getUpperBound(var) << ")"; + } + Debug("arith::tracking") << endl; + } + Debug("arith::tracking") << "end row"<< endl; + + if(basicIsTracked(basic)){ + BoundCounts computed = computeBoundCounts(basic); + Debug("arith::tracking") + << "computed " << computed + << " tracking " << d_boundTracking[basic] << endl; + Assert(computed == d_boundTracking[basic]); + + } + } +} void LinearEqualityModule::debugPivot(ArithVar x_i, ArithVar x_j){ Debug("arith::pivot") << "debugPivot("<< x_i <<"|->"<< x_j << ")" << endl; @@ -143,13 +363,13 @@ void LinearEqualityModule::debugPivot(ArithVar x_i, ArithVar x_j){ ArithVar var = entry.getColVar(); const Rational& coeff = entry.getCoefficient(); - DeltaRational beta = d_partialModel.getAssignment(var); + DeltaRational beta = d_variables.getAssignment(var); Debug("arith::pivot") << var << beta << coeff; - if(d_partialModel.hasLowerBound(var)){ - Debug("arith::pivot") << "(lb " << d_partialModel.getLowerBound(var) << ")"; + if(d_variables.hasLowerBound(var)){ + Debug("arith::pivot") << "(lb " << d_variables.getLowerBound(var) << ")"; } - if(d_partialModel.hasUpperBound(var)){ - Debug("arith::pivot") << "(up " << d_partialModel.getUpperBound(var) << ")"; + if(d_variables.hasUpperBound(var)){ + Debug("arith::pivot") << "(up " << d_variables.getUpperBound(var) << ")"; } Debug("arith::pivot") << endl; } @@ -177,11 +397,11 @@ void LinearEqualityModule::debugCheckTableau(){ if(basic == nonbasic) continue; const Rational& coeff = entry.getCoefficient(); - DeltaRational beta = d_partialModel.getAssignment(nonbasic); + DeltaRational beta = d_variables.getAssignment(nonbasic); Debug("paranoid:check_tableau") << nonbasic << beta << coeff< 0) : (sgn < 0); const DeltaRational& bound = ub ? - d_partialModel.getUpperBound(nonbasic): - d_partialModel.getLowerBound(nonbasic); + d_variables.getUpperBound(nonbasic): + d_variables.getLowerBound(nonbasic); DeltaRational diff = bound * coeff; sum = sum + diff; @@ -239,7 +459,7 @@ DeltaRational LinearEqualityModule::computeRowValue(ArithVar x, bool useSafe){ if(nonbasic == x) continue; const Rational& coeff = entry.getCoefficient(); - const DeltaRational& assignment = d_partialModel.getAssignment(nonbasic, useSafe); + const DeltaRational& assignment = d_variables.getAssignment(nonbasic, useSafe); sum = sum + (assignment * coeff); } return sum; @@ -253,13 +473,13 @@ bool LinearEqualityModule::hasBounds(ArithVar basic, bool upperBound){ if(var == basic) continue; int sgn = entry.getCoefficient().sgn(); if(upperBound){ - if( (sgn < 0 && !d_partialModel.hasLowerBound(var)) || - (sgn > 0 && !d_partialModel.hasUpperBound(var))){ + if( (sgn < 0 && !d_variables.hasLowerBound(var)) || + (sgn > 0 && !d_variables.hasUpperBound(var))){ return false; } }else{ - if( (sgn < 0 && !d_partialModel.hasUpperBound(var)) || - (sgn > 0 && !d_partialModel.hasLowerBound(var))){ + if( (sgn < 0 && !d_variables.hasUpperBound(var)) || + (sgn > 0 && !d_variables.hasLowerBound(var))){ return false; } } @@ -272,6 +492,8 @@ void LinearEqualityModule::propagateNonbasics(ArithVar basic, Constraint c){ Assert(d_tableau.isBasic(basic)); Assert(c->getVariable() == basic); Assert(!c->assertedToTheTheory()); + Assert(!upperBound || c->isUpperBound()); // upperbound implies c is an upperbound + Assert(upperBound || c->isLowerBound()); // !upperbound implies c is a lowerbound //Assert(c->canBePropagated()); Assert(!c->hasProof()); @@ -293,17 +515,17 @@ void LinearEqualityModule::propagateNonbasics(ArithVar basic, Constraint c){ Constraint bound = NullConstraint; if(upperBound){ if(sgn < 0){ - bound = d_partialModel.getLowerBoundConstraint(nonbasic); + bound = d_variables.getLowerBoundConstraint(nonbasic); }else{ Assert(sgn > 0); - bound = d_partialModel.getUpperBoundConstraint(nonbasic); + bound = d_variables.getUpperBoundConstraint(nonbasic); } }else{ if(sgn < 0){ - bound = d_partialModel.getUpperBoundConstraint(nonbasic); + bound = d_variables.getUpperBoundConstraint(nonbasic); }else{ Assert(sgn > 0); - bound = d_partialModel.getLowerBoundConstraint(nonbasic); + bound = d_variables.getLowerBoundConstraint(nonbasic); } } Assert(bound != NullConstraint); @@ -315,6 +537,982 @@ void LinearEqualityModule::propagateNonbasics(ArithVar basic, Constraint c){ << basic << ") done" << endl; } +Constraint 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); + + Constraint c = ub ? + d_variables.getUpperBoundConstraint(v) : + d_variables.getLowerBoundConstraint(v); + + bool weakened; + do{ + const DeltaRational& bound = c->getValue(); + + weakened = false; + + Constraint 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; + + Debug("weak") << "found:" << endl; + if(v == basic){ + Debug("weak") << " basic: "; + } + Debug("weak") << " " << surplus << " "<< diff << endl + << " " << bound << c << endl + << " " << weakerBound << weaker << endl; + + Assert(diff.sgn() > 0); + c = weaker; + } + } + }while(weakened); + + return c; +} + +Node LinearEqualityModule::minimallyWeakConflict(bool aboveUpper, ArithVar basicVar) const { + TimerStat::CodeTimer codeTimer(d_statistics.d_weakenTime); + + 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; + } + + NodeBuilder<> conflict(kind::AND); + 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; + Constraint c = weakestExplanation(aboveUpper, surplus, v, coeff, weakening, basicVar); + Debug("weak") << "weak : " << weakening << " " + << c->assertedToTheTheory() << " " + << d_variables.getAssignment(v) << " " + << c << endl + << c->explainForConflict() << endl; + anyWeakenings = anyWeakenings || weakening; + + Debug("weak") << "weak : " << c->explainForConflict() << endl; + c->explainForConflict(conflict); + } + ++d_statistics.d_weakeningAttempts; + if(anyWeakenings){ + ++d_statistics.d_weakeningSuccesses; + } + return conflict; +} + +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(Debug.isOn("arith::tracking")){ + debugCheckTracking(); + } + Assert(d_areTracking); +} + +void LinearEqualityModule::stopTrackingBoundCounts(){ + Assert(d_areTracking); + d_areTracking = false; + if(Debug.isOn("arith::tracking")){ + debugCheckTracking(); + } + Assert(!d_areTracking); +} + + +void LinearEqualityModule::trackVariable(ArithVar x_i){ + Assert(!basicIsTracked(x_i)); + BoundCounts counts(0,0); + + 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(); + counts += (d_variables.oldBoundCounts(nonbasic)).multiplyBySgn(a_ij.sgn()); + } + d_boundTracking.set(x_i, counts); +} + +BoundCounts LinearEqualityModule::computeBoundCounts(ArithVar x_i) const{ + BoundCounts counts(0,0); + + 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(); + counts += (d_variables.boundCounts(nonbasic)).multiplyBySgn(a_ij.sgn()); + } + + return counts; +} + +// BoundCounts LinearEqualityModule::cachingCountBounds(ArithVar x_i) const{ +// if(d_boundTracking.isKey(x_i)){ +// return d_boundTracking[x_i]; +// }else{ +// return computeBoundCounts(x_i); +// } +// } +BoundCounts LinearEqualityModule::_countBounds(ArithVar x_i) const { + Assert(d_boundTracking.isKey(x_i)); + return d_boundTracking[x_i]; +} + +// BoundCounts LinearEqualityModule::countBounds(ArithVar x_i){ +// if(d_boundTracking.isKey(x_i)){ +// return d_boundTracking[x_i]; +// }else{ +// BoundCounts bc = computeBoundCounts(x_i); +// if(d_areTracking){ +// d_boundTracking.set(x_i,bc); +// } +// return bc; +// } +// } + +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(); + + Constraint c = u.limiting(); + int toUB = (c->getType() == UpperBound || + c->getType() == Equality) ? 1 : 0; + int toLB = (c->getType() == LowerBound || + c->getType() == Equality) ? 1 : 0; + + + BoundCounts bcs = d_boundTracking[basic]; + // x = c*n + \sum d*m + // n = 1/c * x + -1/c * (\sum d*m) + BoundCounts nonb = bcs - d_variables.boundCounts(nonbasic).multiplyBySgn(coeffSgn); + nonb = nonb.multiplyBySgn(-coeffSgn); + nonb += BoundCounts(toLB, toUB).multiplyBySgn(coeffSgn); + + uint32_t length = d_tableau.basicRowLength(basic); + Debug("basicsAtBounds") + << "bcs " << bcs + << "nonb " << nonb + << "length " << length << endl; + + if(nbdir < 0){ + return bcs.atLowerBounds() + 1 == length; + }else{ + Assert(nbdir > 0); + return bcs.atUpperBounds() + 1 == length; + } +} + +bool LinearEqualityModule::nonbasicsAtLowerBounds(ArithVar basic) const { + Assert(basicIsTracked(basic)); + BoundCounts bcs = d_boundTracking[basic]; + uint32_t length = d_tableau.basicRowLength(basic); + + return bcs.atLowerBounds() + 1 == length; +} + +bool LinearEqualityModule::nonbasicsAtUpperBounds(ArithVar basic) const { + Assert(basicIsTracked(basic)); + BoundCounts bcs = d_boundTracking[basic]; + uint32_t length = d_tableau.basicRowLength(basic); + + return bcs.atUpperBounds() + 1 == length; +} + +void LinearEqualityModule::trackingSwap(ArithVar basic, ArithVar nb, int nbSgn) { + Assert(basicIsTracked(basic)); + + // z = a*x + \sum b*y + // x = (1/a) z + \sum (-1/a)*b*y + // basicCount(z) = bc(a*x) + bc(\sum b y) + // basicCount(x) = bc(z/a) + bc(\sum -b/a * y) + + // sgn(1/a) = sgn(a) + // bc(a*x) = bc(x).multiply(sgn(a)) + // bc(z/a) = bc(z).multiply(sgn(a)) + // bc(\sum -b/a * y) = bc(\sum b y).multiplyBySgn(-sgn(a)) + // bc(\sum b y) = basicCount(z) - bc(a*x) + // basicCount(x) = + // = bc(z).multiply(sgn(a)) + (basicCount(z) - bc(a*x)).multiplyBySgn(-sgn(a)) + + BoundCounts bc = d_boundTracking[basic]; + bc -= (d_variables.boundCounts(nb)).multiplyBySgn(nbSgn); + bc = bc.multiplyBySgn(-nbSgn); + bc += d_variables.boundCounts(basic).multiplyBySgn(nbSgn); + d_boundTracking.set(nb, bc); + d_boundTracking.remove(basic); +} + +void LinearEqualityModule::trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn){ + Assert(oldSgn != currSgn); + BoundCounts nb_bc = d_variables.boundCounts(nb); + + if(!nb_bc.isZero()){ + ArithVar basic = d_tableau.rowIndexToBasic(ridx); + Assert(basicIsTracked(basic)); + + BoundCounts& basic_bc = d_boundTracking.get(basic); + basic_bc.addInSgn(nb_bc, oldSgn, currSgn); + } +} + +void LinearEqualityModule::computeSafeUpdate(UpdateInfo& inf, VarPreferenceFunction pref){ + ArithVar nb = inf.nonbasic(); + int sgn = inf.nonbasicDirection(); + Assert(sgn != 0); + Assert(!d_tableau.isBasic(nb)); + + //inf.setErrorsChange(0); + //inf.setlimiting = NullConstraint; + + + // Error variables moving in the correct direction + Assert(d_relevantErrorBuffer.empty()); + + // phases : + enum ComputeSafePhase { + NoBoundSelected, + NbsBoundSelected, + BasicBoundSelected, + DegenerateBoundSelected + } phase; + + phase = NoBoundSelected; + + static int instance = 0; + Debug("computeSafeUpdate") << "computeSafeUpdate " << (++instance) << endl; + + if(sgn > 0 && d_variables.hasUpperBound(nb)){ + Constraint ub = d_variables.getUpperBoundConstraint(nb); + inf.updatePureFocus(ub->getValue() - d_variables.getAssignment(nb), ub); + + Assert(inf.nonbasicDelta().sgn() == sgn); + Debug("computeSafeUpdate") << "computeSafeUpdate " << inf.limiting() << endl; + phase = NbsBoundSelected; + }else if(sgn < 0 && d_variables.hasLowerBound(nb)){ + Constraint lb = d_variables.getLowerBoundConstraint(nb); + inf.updatePureFocus(lb->getValue() - d_variables.getAssignment(nb), lb); + + Assert(inf.nonbasicDelta().sgn() == sgn); + + Debug("computeSafeUpdate") << "computeSafeUpdate " << inf.limiting() << endl; + phase = NbsBoundSelected; + } + + Tableau::ColIterator basicIter = d_tableau.colIterator(nb); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + Assert(entry.getColVar() == nb); + + ArithVar basic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + const Rational& a_ji = entry.getCoefficient(); + int basic_movement = sgn * a_ji.sgn(); + + Debug("computeSafeUpdate") + << "computeSafeUpdate: " + << basic << ", " + << basic_movement << ", " + << d_variables.cmpAssignmentUpperBound(basic) << ", " + << d_variables.cmpAssignmentLowerBound(basic) << ", " + << a_ji << ", " + << d_variables.getAssignment(basic) << endl; + + Constraint proposal = NullConstraint; + + if(basic_movement > 0){ + if(d_variables.cmpAssignmentLowerBound(basic) < 0){ + d_relevantErrorBuffer.push_back(&entry); + } + if(d_variables.hasUpperBound(basic) && + d_variables.cmpAssignmentUpperBound(basic) <= 0){ + proposal = d_variables.getUpperBoundConstraint(basic); + } + }else if(basic_movement < 0){ + if(d_variables.cmpAssignmentUpperBound(basic) > 0){ + d_relevantErrorBuffer.push_back(&entry); + } + if(d_variables.hasLowerBound(basic) && + d_variables.cmpAssignmentLowerBound(basic) >= 0){ + proposal = d_variables.getLowerBoundConstraint(basic); + } + } + if(proposal != NullConstraint){ + const Rational& coeff = entry.getCoefficient(); + DeltaRational diff = proposal->getValue() - d_variables.getAssignment(basic); + diff /= coeff; + int cmp = phase == NoBoundSelected ? 0 : diff.cmp(inf.nonbasicDelta()); + Assert(diff.sgn() == sgn || diff.sgn() == 0); + bool prefer = false; + switch(phase){ + case NoBoundSelected: + prefer = true; + break; + case NbsBoundSelected: + prefer = (sgn > 0 && cmp < 0 ) || (sgn < 0 && cmp > 0); + break; + case BasicBoundSelected: + prefer = + (sgn > 0 && cmp < 0 ) || + (sgn < 0 && cmp > 0) || + (cmp == 0 && basic == (this->*pref)(basic, inf.leaving())); + break; + case DegenerateBoundSelected: + prefer = cmp == 0 && basic == (this->*pref)(basic, inf.leaving()); + break; + } + if(prefer){ + inf.updatePivot(diff, coeff, proposal); + + phase = (diff.sgn() != 0) ? BasicBoundSelected : DegenerateBoundSelected; + } + } + } + + if(phase == DegenerateBoundSelected){ + inf.setErrorsChange(0); + }else{ + computedFixed(inf); + } + inf.determineFocusDirection(); + + d_relevantErrorBuffer.clear(); +} + +void LinearEqualityModule::computedFixed(UpdateInfo& proposal){ + Assert(proposal.nonbasicDirection() != 0); + Assert(!d_tableau.isBasic(proposal.nonbasic())); + + //bool unconstrained = (proposal.d_limiting == NullConstraint); + + Assert(!proposal.unbounded() || !d_relevantErrorBuffer.empty()); + + Assert(proposal.unbounded() || + proposal.nonbasicDelta().sgn() == proposal.nonbasicDirection()); + + // proposal.d_value is the max + + UpdateInfo max; + int dropped = 0; + //Constraint maxFix = NullConstraint; + //DeltaRational maxAmount; + + EntryPointerVector::const_iterator i = d_relevantErrorBuffer.begin(); + EntryPointerVector::const_iterator i_end = d_relevantErrorBuffer.end(); + for(; i != i_end; ++i){ + const Tableau::Entry& entry = *(*i); + Assert(entry.getColVar() == proposal.nonbasic()); + + ArithVar basic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + const Rational& a_ji = entry.getCoefficient(); + int basic_movement = proposal.nonbasicDirection() * a_ji.sgn(); + + DeltaRational theta; + DeltaRational proposedValue; + if(!proposal.unbounded()){ + theta = proposal.nonbasicDelta() * a_ji; + proposedValue = theta + d_variables.getAssignment(basic); + } + + Constraint fixed = NullConstraint; + + if(basic_movement < 0){ + Assert(d_variables.cmpAssignmentUpperBound(basic) > 0); + + if(proposal.unbounded() || d_variables.cmpToUpperBound(basic, proposedValue) <= 0){ + --dropped; + fixed = d_variables.getUpperBoundConstraint(basic); + } + }else if(basic_movement > 0){ + Assert(d_variables.cmpAssignmentLowerBound(basic) < 0); + + if(proposal.unbounded() || d_variables.cmpToLowerBound(basic, proposedValue) >= 0){ + --dropped; + fixed = d_variables.getLowerBoundConstraint(basic); + } + } + if(fixed != NullConstraint){ + DeltaRational amount = fixed->getValue() - d_variables.getAssignment(basic); + amount /= a_ji; + Assert(amount.sgn() == proposal.nonbasicDirection()); + + if(max.uninitialized()){ + max = UpdateInfo(proposal.nonbasic(), proposal.nonbasicDirection()); + max.updatePivot(amount, a_ji, fixed, dropped); + }else{ + int cmp = amount.cmp(max.nonbasicDelta()); + bool prefer = + (proposal.nonbasicDirection() < 0 && cmp < 0) || + (proposal.nonbasicDirection() > 0 && cmp > 0) || + (cmp == 0 && fixed->getVariable() < max.limiting()->getVariable()); + + if(prefer){ + max.updatePivot(amount, a_ji, fixed, dropped); + }else{ + max.setErrorsChange(dropped); + } + } + } + } + Assert(dropped < 0 || !proposal.unbounded()); + + if(dropped < 0){ + proposal = max; + }else{ + Assert(dropped == 0); + Assert(proposal.nonbasicDelta().sgn() != 0); + Assert(proposal.nonbasicDirection() != 0); + proposal.setErrorsChange(0); + } + Assert(proposal.errorsChange() == dropped); +} + +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)); + + Constraint 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){ + Debug("handleBorders") << "push back increasing " << border << endl; + d_increasing.push_back(border); + }else{ + Debug("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.nothing() || nbDiff <= d_upperBoundDifference){ + return false; + } + }else{ + if(d_lowerBoundDifference.nothing() || nbDiff >= d_lowerBoundDifference){ + return false; + } + } + + // Assume past this point, nb will be in error if this pivot is done + ArithVar nb = entry.getColVar(); + ArithVar basic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + Assert(basicIsTracked(basic)); + 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) + + Assert(basicIsTracked(basic)); + BoundCounts bc = d_boundTracking[basic]; + + // 1) y = a * x + \sum b * z + // Get bc(\sum b * z) + BoundCounts sumOnly = bc - d_variables.boundCounts(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) + BoundCounts withNegY = sumOnly + ysBounds.multiplyBySgn(-1); + + // 3) x = (-1/a) * ( -y + \sum b * z) + // Get bc((-1/a) * ( -y + \sum b * z)) + BoundCounts xsBoundsAfterPivot = withNegY.multiplyBySgn(-coeffSgn); + + uint32_t length = d_tableau.basicRowLength(basic); + if(nbSgn > 0){ + // Only check for the upper bound being violated + return xsBoundsAfterPivot.atLowerBounds() + 1 == length; + }else{ + // Only check for the lower bound being violated + return xsBoundsAfterPivot.atUpperBounds() + 1 == length; + } +} + +UpdateInfo LinearEqualityModule::mkConflictUpdate(const Tableau::Entry& entry, bool ub) const{ + ArithVar currBasic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + ArithVar nb = entry.getColVar(); + + Constraint 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.nothing()); + Assert(d_upperBoundDifference.nothing()); + + int focusCoeffSgn = focusCoeff.sgn(); + + static int instance = 0; + ++instance; + Debug("speculativeUpdate") << "speculativeUpdate " << instance << endl; + Debug("speculativeUpdate") << "nb " << nb << endl; + Debug("speculativeUpdate") << "focusCoeff " << focusCoeff << endl; + + if(d_variables.hasUpperBound(nb)){ + Constraint ub = d_variables.getUpperBoundConstraint(nb); + d_upperBoundDifference = ub->getValue() - d_variables.getAssignment(nb); + Border border(ub, d_upperBoundDifference, false, NULL, true); + Debug("handleBorders") << "push back increasing " << border << endl; + d_increasing.push_back(border); + } + if(d_variables.hasLowerBound(nb)){ + Constraint lb = d_variables.getLowerBoundConstraint(nb); + d_lowerBoundDifference = lb->getValue() - d_variables.getAssignment(nb); + Border border(lb, d_lowerBoundDifference, false, NULL, false); + Debug("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.clear(); + d_upperBoundDifference.clear(); +} + +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(); + + Debug("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; + + Debug("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; + + Debug("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); +} + }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/arith/linear_equality.h b/src/theory/arith/linear_equality.h index 14df8d819..8b9b888f2 100644 --- a/src/theory/arith/linear_equality.h +++ b/src/theory/arith/linear_equality.h @@ -27,36 +27,198 @@ #include "cvc4_private.h" -#ifndef __CVC4__THEORY__ARITH__LINEAR_EQUALITY_H -#define __CVC4__THEORY__ARITH__LINEAR_EQUALITY_H +#pragma once #include "theory/arith/delta_rational.h" #include "theory/arith/arithvar.h" #include "theory/arith/partial_model.h" -#include "theory/arith/matrix.h" -#include "theory/arith/constraint.h" +#include "theory/arith/tableau.h" +#include "theory/arith/constraint_forward.h" +#include "theory/arith/simplex_update.h" +#include "theory/arith/options.h" +#include "util/maybe.h" #include "util/statistics_registry.h" namespace CVC4 { namespace theory { namespace arith { +struct Border{ + // The constraint for the border + Constraint 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(Constraint 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(Constraint 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(){ + std::remove_if(d_vec.begin(), d_vec.end(), &Border::nonZero); + } + + 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. */ - ArithPartialModel& d_partialModel; + ArithVariables& d_variables; - /** - * Reference to the Tableau to operate upon. - */ + /** Reference to the Tableau to operate upon. */ Tableau& d_tableau; /** Called whenever the value of a basic variable is updated. */ - ArithVarCallBack& d_basicVariableUpdates; + BasicVarModelUpdateCallBack d_basicVariableUpdates; + + BorderHeap d_increasing; + BorderHeap d_decreasing; + Maybe d_upperBoundDifference; + Maybe d_lowerBoundDifference; public: @@ -64,15 +226,22 @@ public: * Initializes a LinearEqualityModule with a partial model, a tableau, * and a callback function for when basic variables update their values. */ - LinearEqualityModule(ArithPartialModel& pm, Tableau& t, ArithVarCallBack& f): - d_partialModel(pm), d_tableau(t), d_basicVariableUpdates(f) - {} + LinearEqualityModule(ArithVariables& vars, Tableau& t, BoundCountingVector& 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); + void updateUntracked(ArithVar x_i, const DeltaRational& v); + void updateTracked(ArithVar x_i, const DeltaRational& v); + void update(ArithVar x_i, const DeltaRational& v){ + if(d_areTracking){ + updateTracked(x_i,v); + }else{ + updateUntracked(x_i,v); + } + } + void updateMany(const DenseMap& many); /** * Updates the value of a basic variable x_i to v, @@ -80,11 +249,12 @@ public: * Updates the assignment of the other basic variables accordingly. */ void pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& v); + //void pivotAndUpdateAdj(ArithVar x_i, ArithVar x_j, const DeltaRational& v); - - ArithPartialModel& getPartialModel() const{ return d_partialModel; } + ArithVariables& getVariables() const{ return d_variables; } Tableau& getTableau() const{ return d_tableau; } + void forceNewBasis(const DenseSet& newBasis); bool hasBounds(ArithVar basic, bool upperBound); bool hasLowerBounds(ArithVar basic){ @@ -94,7 +264,180 @@ public: return hasBounds(basic, true); } + void startTrackingBoundCounts(); + void stopTrackingBoundCounts(); + + + void includeBoundCountChange(ArithVar nb, BoundCounts prev); + + void computeSafeUpdate(UpdateInfo& inf, VarPreferenceFunction basic); + + + 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()); + } + } + + // template + // bool preferNonDegenerate(const UpdateInfo& a, const UpdateInfo& b) const{ + // if(a.focusDirection() == b.focusDirection()){ + // if(heuristic){ + // return preferNeitherBound(a,b); + // }else{ + // return minNonBasicVarOrder(a,b); + // } + // }else{ + // return a.focusDirection() < b.focusDirection(); + // } + // } + + // template + // bool preferErrorsFixed(const UpdateInfo& a, const UpdateInfo& b) const{ + // if( a.errorsChange() == b.errorsChange() ){ + // return preferNonDegenerate(a,b); + // }else{ + // return a.errorsChange() > b.errorsChange(); + // } + // } + + // template + // bool preferConflictFound(const UpdateInfo& a, const UpdateInfo& b) const{ + // if(a.d_foundConflict && b.d_foundConflict){ + // // if both are true, determinize the preference + // return minNonBasicVarOrder(a,b); + // }else if( a.d_foundConflict || b.d_foundConflict ){ + // return b.d_foundConflict; + // }else{ + // return preferErrorsFixed(a,b); + // } + // } + + 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: + typedef std::vector EntryPointerVector; + EntryPointerVector d_relevantErrorBuffer; + + //uint32_t computeUnconstrainedUpdate(ArithVar nb, int sgn, DeltaRational& am); + //uint32_t computedFixed(ArithVar nb, int sgn, const DeltaRational& am); + void computedFixed(UpdateInfo&); + + // RowIndex -> BoundCount + BoundCountingVector& d_boundTracking; + bool d_areTracking; + /** * Exports either the explanation of an upperbound or a lower bound * of the basic variable basic, using the non-basic variables in the row. @@ -104,11 +447,9 @@ private: public: void propagateNonbasicsLowerBound(ArithVar basic, Constraint c){ - Assert(c->isLowerBound()); propagateNonbasics(basic, c); } void propagateNonbasicsUpperBound(ArithVar basic, Constraint c){ - Assert(c->isUpperBound()); propagateNonbasics(basic, c); } @@ -128,42 +469,284 @@ public: return computeBound(basic, true); } + /** + * 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 basicIsTracked(ArithVar v) const { + return d_boundTracking.isKey(v); + } + void trackVariable(ArithVar x_i); + + void maybeRemoveTracking(ArithVar v){ + Assert(!d_tableau.isBasic(v)); + if(d_boundTracking.isKey(v)){ + d_boundTracking.remove(v); + } + } + + // void trackVariable(ArithVar x_i){ + // Assert(!basicIsTracked(x_i)); + // d_boundTracking.set(x_i,computeBoundCounts(x_i)); + // } + bool basicsAtBounds(const UpdateInfo& u) const; +private: + BoundCounts computeBoundCounts(ArithVar x_i) const; +public: + //BoundCounts cachingCountBounds(ArithVar x_i) const; + BoundCounts _countBounds(ArithVar x_i) const; + void trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn); + + void trackingSwap(ArithVar basic, ArithVar nb, int sgn); + + bool nonbasicsAtLowerBounds(ArithVar x_i) const; + bool nonbasicsAtUpperBounds(ArithVar x_i) const; + + ArithVar _anySlackLowerBound(ArithVar x_i) const { + return selectSlack(x_i, &LinearEqualityModule::noPreference); + } + ArithVar _anySlackUpperBound(ArithVar x_i) const { + return selectSlack(x_i, &LinearEqualityModule::noPreference); + } + +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){ + d_linEq->trackingCoefficientChange(ridx, nb, oldSgn, currSgn); + } + void swap(ArithVar basic, ArithVar nb, int oldNbSgn){ + d_linEq->trackingSwap(basic, nb, oldNbSgn); + } + bool canUseRow(RowIndex ridx) const { + 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. + */ + Constraint 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. + */ + Node minimallyWeakConflict(bool aboveUpper, ArithVar basicVar) const; + + /** + * Given a non-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 Node generateConflictAboveUpperBound(ArithVar conflictVar) const { + return minimallyWeakConflict(true, conflictVar); + } + + inline Node generateConflictBelowLowerBound(ArithVar conflictVar) const { + return minimallyWeakConflict(false, conflictVar); + } + private: DeltaRational computeBound(ArithVar basic, bool upperBound); 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); + /** Checks the tableau + partial model for consistency. */ + bool debugEntireLinEqIsConsistent(const std::string& s); + + + 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 debugEntireLinEqIsConsistent(const std::string& s); + 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; Statistics(); ~Statistics(); }; - Statistics d_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; +public: + CompPenaltyColLength(LinearEqualityModule* mod): d_mod(mod){} + + bool operator()(const Cand& x, const Cand& y) const { + if(x.d_penalty == y.d_penalty || !options::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 BoundCountsCallback { +private: + LinearEqualityModule* d_mod; +public: + UpdateTrackingCallback(LinearEqualityModule* mod): d_mod(mod){} + void operator()(ArithVar v, BoundCounts bc){ + d_mod->includeBoundCountChange(v, bc); + } +}; + }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ - -#endif /* __CVC4__THEORY__ARITH__LINEAR_EQUALITY_H */ diff --git a/src/theory/arith/matrix.cpp b/src/theory/arith/matrix.cpp index 9fee74324..7136c3fa8 100644 --- a/src/theory/arith/matrix.cpp +++ b/src/theory/arith/matrix.cpp @@ -23,551 +23,9 @@ namespace CVC4 { namespace theory { namespace arith { - -/* -void Tableau::addRow(ArithVar basicVar, - const std::vector& coeffs, - const std::vector& variables){ - - Assert(coeffs.size() == variables.size()); - - //The new basic variable cannot already be a basic variable - Assert(!d_basicVariables.isMember(basicVar)); - d_basicVariables.add(basicVar); - ReducedRowVector* row_current = new ReducedRowVector(basicVar,variables, coeffs,d_rowCount, d_columnMatrix); - d_rowsTable[basicVar] = row_current; - - //A variable in the row may have been made non-basic already. - //If this is the case we fake pivoting this variable - vector::const_iterator varsIter = variables.begin(); - vector::const_iterator varsEnd = variables.end(); - - for( ; varsIter != varsEnd; ++varsIter){ - ArithVar var = *varsIter; - - if(d_basicVariables.isMember(var)){ - EntryID varID = find(basicVar, var); - TableauEntry& entry = d_entryManager.get(varID); - const Rational& coeff = entry.getCoefficient(); - - loadRowIntoMergeBuffer(var); - rowPlusRowTimesConstant(coeff, basicVar, var); - emptyRowFromMergeBuffer(var); - } - } -} -*/ - -/* -ReducedRowVector* Tableau::removeRow(ArithVar basic){ - Assert(isBasic(basic)); - - ReducedRowVector* row = d_rowsTable[basic]; - - d_basicVariables.remove(basic); - d_rowsTable[basic] = NULL; - - return row; -} -*/ - -void Tableau::pivot(ArithVar oldBasic, ArithVar newBasic){ - Assert(isBasic(oldBasic)); - Assert(!isBasic(newBasic)); - Assert(d_mergeBuffer.empty()); - - Debug("tableau") << "Tableau::pivot(" << oldBasic <<", " << newBasic <<")" << endl; - - RowIndex ridx = basicToRowIndex(oldBasic); - - rowPivot(oldBasic, newBasic); - 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(); - - 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); -} - -// void Tableau::printTableau() const { -// Debug("tableau") << "Tableau::d_activeRows" << endl; - -// ArithVarSet::const_iterator basicIter = beginBasic(), endIter = endBasic(); -// for(; basicIter != endIter; ++basicIter){ -// ArithVar basic = *basicIter; -// printRow(basic); -// } -// } - -// void Tableau::printRow(ArithVar basic) const { -// Debug("tableau") << "{" << basic << ":"; -// for(RowIterator entryIter = rowIterator(basic); !entryIter.atEnd(); ++entryIter){ -// const TableauEntry& entry = *entryIter; -// printEntry(entry); -// Debug("tableau") << ","; -// } -// Debug("tableau") << "}" << endl; -// } - -// void Tableau::printEntry(const TableauEntry& entry) const { -// Debug("tableau") << entry.getColVar() << "*" << entry.getCoefficient(); -// } - -// uint32_t Tableau::numNonZeroEntriesByRow() const { -// uint32_t rowSum = 0; -// ArithVarSet::const_iterator i = d_basicVariables.begin(), end = d_basicVariables.end(); -// for(; i != end; ++i){ -// ArithVar basic = *i; -// rowSum += getRowLength(basic); -// } -// return rowSum; -// } - -// uint32_t Tableau::numNonZeroEntriesByCol() const { -// uint32_t colSum = 0; -// VectorSizeTable::const_iterator i = d_colLengths.begin(); -// VectorSizeTable::const_iterator end = d_colLengths.end(); -// for(; i != end; ++i){ -// colSum += *i; -// } -// return colSum; -// } - - -// EntryID Tableau::findOnRow(ArithVar row, ArithVar col){ -// for(RowIterator i = rowIterator(row); !i.atEnd(); ++i){ -// EntryID id = i.getID(); -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); - -// if(colVar == col){ -// return id; -// } -// } -// return ENTRYID_SENTINEL; -// } - -// EntryID Tableau::findOnCol(ArithVar row, ArithVar col){ -// for(ColIterator i = colIterator(col); !i.atEnd(); ++i){ -// EntryID id = i.getID(); -// const TableauEntry& entry = *i; -// ArithVar rowVar = entry.getRowVar(); - -// if(rowVar == row){ -// return id; -// } -// } -// return ENTRYID_SENTINEL; -// } - -// const TableauEntry& Tableau::findEntry(ArithVar row, ArithVar col){ -// bool colIsShorter = getColLength(col) < getRowLength(row); -// EntryID id = colIsShorter ? findOnCol(row,col) : findOnRow(row,col); -// if(id == ENTRYID_SENTINEL){ -// return d_failedFind; -// }else{ -// return d_entryManager.get(id); -// } -// } - -// void Tableau::removeRow(ArithVar basic){ -// RowIterator i = rowIterator(basic); -// while(!i.atEnd()){ -// EntryID id = i.getID(); -// ++i; -// removeEntry(id); -// } -// d_basicVariables.remove(basic); -// } - -// void Tableau::loadRowIntoMergeBuffer(ArithVar basic){ -// Assert(mergeBufferIsEmpty()); -// for(RowIterator i = rowIterator(basic); !i.atEnd(); ++i){ -// EntryID id = i.getID(); -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); -// d_mergeBuffer[colVar] = make_pair(id, false); -// } -// } - -// void Tableau::emptyRowFromMergeBuffer(ArithVar basic){ -// Assert(isBasic(basic)); -// for(RowIterator i = rowIterator(basic); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); -// Assert(d_mergeBuffer[colVar].first == i.getID()); -// d_mergeBuffer[colVar] = make_pair(ENTRYID_SENTINEL, false); -// } - -// Assert(mergeBufferIsEmpty()); -// } - - -/** - * Changes basic to newbasic (a variable on the row). - */ -void Tableau::rowPivot(ArithVar basicOld, ArithVar basicNew){ - 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); - Rational negInverseA_rs = -(newBasicEntry.getCoefficient().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); -} - -// void Tableau::addEntry(ArithVar row, ArithVar col, const Rational& coeff){ -// Assert(coeff != 0); - -// EntryID newId = d_entryManager.newEntry(); -// TableauEntry& newEntry = d_entryManager.get(newId); -// newEntry = TableauEntry( row, col, -// d_rowHeads[row], d_colHeads[col], -// ENTRYID_SENTINEL, ENTRYID_SENTINEL, -// coeff); -// Assert(newEntry.getCoefficient() != 0); - -// Debug("tableau") << "addEntry(" << row << "," << col <<"," << coeff << ")" << endl; - -// ++d_entriesInUse; - -// if(d_rowHeads[row] != ENTRYID_SENTINEL) -// d_entryManager.get(d_rowHeads[row]).setPrevRowID(newId); - -// if(d_colHeads[col] != ENTRYID_SENTINEL) -// d_entryManager.get(d_colHeads[col]).setPrevColID(newId); - -// d_rowHeads[row] = newId; -// d_colHeads[col] = newId; -// ++d_rowLengths[row]; -// ++d_colLengths[col]; -// } - -// void Tableau::removeEntry(EntryID id){ -// Assert(d_entriesInUse > 0); -// --d_entriesInUse; - -// TableauEntry& entry = d_entryManager.get(id); - -// ArithVar row = entry.getRowVar(); -// ArithVar col = entry.getColVar(); - -// Assert(d_rowLengths[row] > 0); -// Assert(d_colLengths[col] > 0); - - -// --d_rowLengths[row]; -// --d_colLengths[col]; - -// EntryID prevRow = entry.getPrevRowID(); -// EntryID prevCol = entry.getPrevColID(); - -// EntryID nextRow = entry.getNextRowID(); -// EntryID nextCol = entry.getNextColID(); - -// if(d_rowHeads[row] == id){ -// d_rowHeads[row] = nextRow; -// } -// if(d_colHeads[col] == id){ -// d_colHeads[col] = nextCol; -// } - -// entry.markBlank(); - -// if(prevRow != ENTRYID_SENTINEL){ -// d_entryManager.get(prevRow).setNextRowID(nextRow); -// } -// if(nextRow != ENTRYID_SENTINEL){ -// d_entryManager.get(nextRow).setPrevRowID(prevRow); -// } - -// if(prevCol != ENTRYID_SENTINEL){ -// d_entryManager.get(prevCol).setNextColID(nextCol); -// } -// if(nextCol != ENTRYID_SENTINEL){ -// d_entryManager.get(nextCol).setPrevColID(prevCol); -// } - -// d_entryManager.freeEntry(id); -// } - -// void Tableau::rowPlusRowTimesConstant(ArithVar basicTo, const Rational& c, ArithVar basicFrom){ - -// Debug("tableau") << "rowPlusRowTimesConstant(" -// << basicTo << "," << c << "," << basicFrom << ")" -// << endl; - -// Assert(debugNoZeroCoefficients(basicTo)); -// Assert(debugNoZeroCoefficients(basicFrom)); - -// Assert(c != 0); -// Assert(isBasic(basicTo)); -// Assert(isBasic(basicFrom)); -// Assert( d_usedList.empty() ); - - -// RowIterator i = rowIterator(basicTo); -// while(!i.atEnd()){ -// EntryID id = i.getID(); -// TableauEntry& entry = d_entryManager.get(id); -// ArithVar colVar = entry.getColVar(); - -// ++i; -// if(bufferPairIsNotEmpty(d_mergeBuffer[colVar])){ -// d_mergeBuffer[colVar].second = true; -// d_usedList.push_back(colVar); - -// EntryID inOtherRow = d_mergeBuffer[colVar].first; -// const TableauEntry& other = d_entryManager.get(inOtherRow); -// entry.getCoefficient() += c * other.getCoefficient(); - -// if(entry.getCoefficient().sgn() == 0){ -// removeEntry(id); -// } -// } -// } - -// for(RowIterator i = rowIterator(basicFrom); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); - -// if(!(d_mergeBuffer[colVar]).second){ -// Rational newCoeff = c * entry.getCoefficient(); -// addEntry(basicTo, colVar, newCoeff); -// } -// } - -// clearUsedList(); - -// if(Debug.isOn("tableau")) { printTableau(); } -// } - -// void Tableau::clearUsedList(){ -// ArithVarArray::iterator i, end; -// for(i = d_usedList.begin(), end = d_usedList.end(); i != end; ++i){ -// ArithVar pos = *i; -// d_mergeBuffer[pos].second = false; -// } -// d_usedList.clear(); -// } - -void Tableau::addRow(ArithVar basic, - const std::vector& coefficients, - const std::vector& variables) -{ - Assert(basic < getNumColumns()); - - 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(Debug.isOn("matrix")){ printMatrix(); } - - 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); - clearBuffer(); - } - } - - if(Debug.isOn("matrix")) { printMatrix(); } - - Assert(debugNoZeroCoefficients(newRow)); - Assert(debugMatchingCountsForRow(newRow)); - Assert(getColLength(basic) == 1); -} - -// bool Tableau::debugNoZeroCoefficients(ArithVar basic){ -// for(RowIterator i=rowIterator(basic); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// if(entry.getCoefficient() == 0){ -// return false; -// } -// } -// return true; -// } -// bool Tableau::debugMatchingCountsForRow(ArithVar basic){ -// for(RowIterator i=rowIterator(basic); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); -// uint32_t count = debugCountColLength(colVar); -// Debug("tableau") << "debugMatchingCountsForRow " -// << basic << ":" << colVar << " " << count -// <<" "<< d_colLengths[colVar] << endl; -// if( count != d_colLengths[colVar] ){ -// return false; -// } -// } -// return true; -// } - - -// uint32_t Tableau::debugCountColLength(ArithVar var){ -// Debug("tableau") << var << " "; -// uint32_t count = 0; -// for(ColIterator i=colIterator(var); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// Debug("tableau") << "(" << entry.getRowVar() << ", " << i.getID() << ") "; -// ++count; -// } -// Debug("tableau") << endl; -// return count; -// } - -// uint32_t Tableau::debugCountRowLength(ArithVar var){ -// uint32_t count = 0; -// for(RowIterator i=rowIterator(var); !i.atEnd(); ++i){ -// ++count; -// } -// return count; -// } - -/* -void ReducedRowVector::enqueueNonBasicVariablesAndCoefficients(std::vector< ArithVar >& variables,std::vector< Rational >& coefficients) const{ - for(const_iterator i=begin(), endEntries=end(); i != endEntries; ++i){ - ArithVar var = (*i).getArithVar(); - const Rational& q = (*i).getCoefficient(); - if(var != basic()){ - variables.push_back(var); - coefficients.push_back(q); - } - } - }*/ - -// Node Tableau::rowAsEquality(ArithVar basic, const ArithVarToNodeMap& map){ -// using namespace CVC4::kind; - -// Assert(getRowLength(basic) >= 2); - -// vector nonBasicPairs; -// for(RowIterator i = rowIterator(basic); !i.atEnd(); ++i){ -// const TableauEntry& entry = *i; -// ArithVar colVar = entry.getColVar(); -// if(colVar == basic) continue; -// Node var = (map.find(colVar))->second; -// Node coeff = mkRationalNode(entry.getCoefficient()); - -// Node mult = NodeBuilder<2>(MULT) << coeff << var; -// nonBasicPairs.push_back(mult); -// } - -// Node sum = Node::null(); -// if(nonBasicPairs.size() == 1 ){ -// sum = nonBasicPairs.front(); -// }else{ -// Assert(nonBasicPairs.size() >= 2); -// NodeBuilder<> sumBuilder(PLUS); -// sumBuilder.append(nonBasicPairs); -// sum = sumBuilder; -// } -// Node basicVar = (map.find(basic))->second; -// return NodeBuilder<2>(EQUAL) << basicVar << sum; -// } - -// double Tableau::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_colHeads.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 TableauEntryManager::freeEntry(EntryID id){ -// Assert(get(id).blank()); -// Assert(d_size > 0); - -// d_freedEntries.push(id); -// --d_size; -// } - -// EntryID TableauEntryManager::newEntry(){ -// EntryID newId; -// if(d_freedEntries.empty()){ -// newId = d_entries.size(); -// d_entries.push_back(TableauEntry()); -// }else{ -// newId = d_freedEntries.front(); -// d_freedEntries.pop(); -// } -// ++d_size; -// return newId; -// } - -void Tableau::removeBasicRow(ArithVar basic){ - RowIndex rid = basicToRowIndex(basic); - - removeRow(rid); - d_basic2RowIndex.remove(basic); - d_rowIndex2basic.remove(rid); - -} - +void NoEffectCCCB::update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) {} +void NoEffectCCCB::swap(ArithVar basic, ArithVar nb, int nbSgn){} +bool NoEffectCCCB::canUseRow(RowIndex ridx) const { return false; } }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ diff --git a/src/theory/arith/matrix.h b/src/theory/arith/matrix.h index 51c2114a0..100f999e0 100644 --- a/src/theory/arith/matrix.h +++ b/src/theory/arith/matrix.h @@ -19,14 +19,9 @@ #pragma once -#include "expr/node.h" - #include "util/index.h" #include "util/dense_map.h" - #include "theory/arith/arithvar.h" -#include "theory/arith/arithvar_node_map.h" -#include "theory/arith/normal_form.h" #include #include @@ -42,6 +37,20 @@ const EntryID ENTRYID_SENTINEL = std::numeric_limits::max(); typedef Index RowIndex; const RowIndex ROW_INDEX_SENTINEL = std::numeric_limits::max(); +class CoefficientChangeCallback { +public: + virtual void update(RowIndex basic, ArithVar nb, int oldSgn, int currSgn) = 0; + virtual void swap(ArithVar basic, ArithVar nb, int nbSgn) = 0; + virtual bool canUseRow(RowIndex ridx) const = 0; +}; + +class NoEffectCCCB : public CoefficientChangeCallback { +public: + void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn); + void swap(ArithVar basic, ArithVar nb, int nbSgn); + bool canUseRow(RowIndex ridx) const; +}; + template class MatrixEntry { private: @@ -334,6 +343,8 @@ 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 @@ -345,6 +356,8 @@ 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 @@ -406,6 +419,45 @@ public: 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: @@ -511,12 +563,12 @@ public: //RowIndex ridx = d_rows.size(); //d_rows.push_back(RowVectorT(&d_entries)); - std::vector::const_iterator coeffIter = coeffs.begin(); + 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 Rational& coeff = *coeffIter; + const T& coeff = *coeffIter; ArithVar var_i = *varsIter; Assert(var_i < getNumColumns()); addEntry(ridx, var_i, coeff); @@ -578,9 +630,10 @@ public: d_mergeBuffer.get(colVar).second = true; const Entry& other = d_entries.get(bufferEntry); - entry.getCoefficient() += mult * other.getCoefficient(); + T& coeff = entry.getCoefficient(); + coeff += mult * other.getCoefficient(); - if(entry.getCoefficient() == d_zero){ + if(coeff.sgn() == 0){ removeEntry(id); } } @@ -607,6 +660,74 @@ public: if(Debug.isOn("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); + + Debug("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(Debug.isOn("matrix")) { printMatrix(); } + } + bool mergeBufferIsClear() const{ RowToPosUsedPairMap::const_iterator i = d_mergeBuffer.begin(); RowToPosUsedPairMap::const_iterator i_end = d_mergeBuffer.end(); @@ -621,7 +742,7 @@ public: protected: - EntryID findOnRow(RowIndex rid, ArithVar column){ + 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(); @@ -635,7 +756,7 @@ protected: return ENTRYID_SENTINEL; } - EntryID findOnCol(RowIndex rid, ArithVar column){ + 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(); @@ -649,63 +770,59 @@ protected: 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){ - bool colIsShorter = getColLength(col) < getRowLength(rid); - EntryID id = colIsShorter ? findOnCol(rid, col) : findOnRow(rid,col); + const MatrixEntry& findEntry(RowIndex rid, ArithVar col) const{ + EntryID id = findEntryID(rid, col); if(id == ENTRYID_SENTINEL){ return d_failedFind; }else{ - return d_entries.get(id); + return d_entries[id]; } } /** * Prints the contents of the Matrix to Debug("matrix") */ - void printMatrix() const { - Debug("matrix") << "Matrix::printMatrix" << std::endl; + void printMatrix(std::ostream& out) const { + out << "Matrix::printMatrix" << std::endl; for(RowIndex i = 0, N = d_rows.size(); i < N; ++i){ - printRow(i); + printRow(i, out); } } + void printMatrix() const { + printMatrix(Debug("matrix")); + } - void printRow(RowIndex rid) const { - Debug("matrix") << "{" << rid << ":"; + 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); - Debug("matrix") << ","; + printEntry(*i, out); + out << ","; } - Debug("matrix") << "}" << std::endl; + out << "}" << std::endl; + } + void printRow(RowIndex rid) const { + printRow(rid, Debug("matrix")); } + void printEntry(const MatrixEntry& entry, std::ostream& out) const { + out << entry.getColVar() << "*" << entry.getCoefficient(); + } void printEntry(const MatrixEntry& entry) const { - Debug("matrix") << entry.getColVar() << "*" << entry.getCoefficient(); + printEntry(entry, Debug("matrix")); } - - -protected: - - // static bool bufferPairIsNotEmpty(const PosUsedPair& p){ - // return !(p.first == ENTRYID_SENTINEL && p.second == false); - // } - - // static bool bufferPairIsEmpty(const PosUsedPair& p){ - // return (p.first == ENTRYID_SENTINEL && p.second == false); - // } - // bool mergeBufferIsEmpty() const { - // return d_mergeBuffer.end() == std::find_if(d_mergeBuffer.begin(), - // d_mergeBuffer.end(), - // bufferPairIsNotEmpty); - // } - public: uint32_t size() const { return d_entriesInUse; @@ -717,6 +834,31 @@ public: 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(); @@ -822,102 +964,6 @@ protected: };/* class Matrix */ - -/** - * 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)){ - Warning() << v << " is basic." << std::endl; - }else{ - Warning() << 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(rid < d_rowIndex2basic.size()); - return d_rowIndex2basic[rid]; - } - - ColIterator colIterator(ArithVar x) const { - return getColumn(x).begin(); - } - - RowIterator basicRowIterator(ArithVar basic) const { - return getRow(basicToRowIndex(basic)).begin(); - } - - /** - * 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); - - void removeBasicRow(ArithVar basic); - -private: - /* Changes the basic variable on the row for basicOld to basicNew. */ - void rowPivot(ArithVar basicOld, ArithVar basicNew); - -};/* class Tableau */ - }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/arith/options b/src/theory/arith/options index 87278fa61..977d6cb32 100644 --- a/src/theory/arith/options +++ b/src/theory/arith/options @@ -25,7 +25,7 @@ option arithHeuristicPivots --heuristic-pivots=N int16_t :default 0 :read-write expert-option arithStandardCheckVarOrderPivots --standard-effort-variable-order-pivots=N int16_t :default -1 :read-write limits the number of pivots in a single invocation of check() at a non-full effort level using Bland's pivot rule -option arithHeuristicPivotRule --heuristic-pivot-rule=RULE ArithHeuristicPivotRule :handler CVC4::theory::arith::stringToArithHeuristicPivotRule :default MINIMUM :handler-include "theory/arith/options_handlers.h" :include "theory/arith/arith_heuristic_pivot_rule.h" +option arithErrorSelectionRule --error-selection-rule=RULE ErrorSelectionRule :handler CVC4::theory::arith::stringToErrorSelectionRule :default MINIMUM_AMOUNT :handler-include "theory/arith/options_handlers.h" :include "theory/arith/arith_heuristic_pivot_rule.h" change the pivot rule for the basic variable (default is 'min', see --pivot-rule help) # The number of pivots before simplex rechecks every basic variable for a conflict @@ -51,6 +51,7 @@ option arithRewriteEq --enable-arith-rewrite-equalities/--disable-arith-rewrite- turns on the preprocessing rewrite turning equalities into a conjunction of inequalities /turns off the preprocessing rewrite turning equalities into a conjunction of inequalities + option arithMLTrick miplib-trick --enable-miplib-trick/--disable-miplib-trick bool :default false turns on the preprocessing step of attempting to infer bounds on miplib problems /turns off the preprocessing step of attempting to infer bounds on miplib problems @@ -58,7 +59,7 @@ option arithMLTrick miplib-trick --enable-miplib-trick/--disable-miplib-trick bo option arithMLTrickSubstitutions miplib-trick-subs --miplib-trick-subs unsigned :default 1 do substitution for miplib 'tmp' vars if defined in <= N eliminated vars -option doCutAllBounded --enable-cut-all-bounded/--disable-cut-all-bounded bool :default false :read-write +option doCutAllBounded --cut-all-bounded bool :default false :read-write turns on the integer solving step of periodically cutting all integer variables that have both upper and lower bounds /turns off the integer solving step of periodically cutting all integer variables that have both upper and lower bounds @@ -68,4 +69,26 @@ option maxCutsInContext --maxCutsInContext unsigned :default 65535 option revertArithModels --revert-arith-models-on-unsat bool :default false Revert the arithmetic model to a known safe model on unsat if one is cached +option havePenalties --fc-penalties bool :default false :read-write + turns on degenerate pivot penalties +/ turns off degenerate pivot penalties + +option useFC --use-fcsimplex bool :default false :read-write + use focusing and converging simplex (FMCAD 2013 submission) + +option useSOI --use-soi bool :default false :read-write + use sum of infeasibility simplex (FMCAD 2013 submission) + +option restrictedPivots --restrict-pivots bool :default true :read-write + have a pivot cap for simplex at effort levels below fullEffort. + +option collectPivots --collect-pivot-stats bool :default false :read-write + collect the pivot history + +option fancyFinal --fancy-final bool :default false :read-write + Tuning how final check works for really hard problems. + +option exportDioDecompositions --dio-decomps bool :default false :read-write + Let skolem variables for integer divisibility constraints leak from the dio solver. + endmodule diff --git a/src/theory/arith/options_handlers.h b/src/theory/arith/options_handlers.h index 1e2ab379a..899a3a7c6 100644 --- a/src/theory/arith/options_handlers.h +++ b/src/theory/arith/options_handlers.h @@ -51,16 +51,16 @@ This decides on kind of propagation arithmetic attempts to do during the search. +both\n\ "; -static const std::string heuristicPivotRulesHelp = "\ -This decides on the rule used by simplex during heuristic rounds\n\ +static const std::string errorSelectionRulesHelp = "\ +This decides on the rule used by simplex during hueristic rounds\n\ for deciding the next basic variable to select.\n\ Heuristic pivot rules available:\n\ +min\n\ The minimum abs() value of the variable's violation of its bound. (default)\n\ -+min-break-ties\n\ - The minimum violation with ties broken by variable order (total)\n\ +max\n\ The maximum violation the bound\n\ ++varord\n\ + The variable order\n\ "; inline ArithUnateLemmaMode stringToArithUnateLemmaMode(std::string option, std::string optarg, SmtEngine* smt) throw(OptionException) { @@ -99,15 +99,15 @@ inline ArithPropagationMode stringToArithPropagationMode(std::string option, std } } -inline ArithHeuristicPivotRule stringToArithHeuristicPivotRule(std::string option, std::string optarg, SmtEngine* smt) throw(OptionException) { +inline ErrorSelectionRule stringToErrorSelectionRule(std::string option, std::string optarg, SmtEngine* smt) throw(OptionException) { if(optarg == "min") { - return MINIMUM; - } else if(optarg == "min-break-ties") { - return BREAK_TIES; + return MINIMUM_AMOUNT; + } else if(optarg == "varord") { + return VAR_ORDER; } else if(optarg == "max") { - return MAXIMUM; + return MAXIMUM_AMOUNT; } else if(optarg == "help") { - puts(heuristicPivotRulesHelp.c_str()); + puts(errorSelectionRulesHelp.c_str()); exit(1); } else { throw OptionException(std::string("unknown option for --heuristic-pivot-rule: `") + diff --git a/src/theory/arith/partial_model.cpp b/src/theory/arith/partial_model.cpp index 6bbaa1e5e..695d9df25 100644 --- a/src/theory/arith/partial_model.cpp +++ b/src/theory/arith/partial_model.cpp @@ -19,6 +19,7 @@ #include "theory/arith/partial_model.h" #include "util/output.h" #include "theory/arith/constraint.h" +#include "theory/arith/normal_form.h" using namespace std; @@ -26,21 +27,164 @@ namespace CVC4 { namespace theory { namespace arith { -ArithPartialModel::ArithPartialModel(context::Context* c, RationalCallBack& deltaComputingFunc) - : d_mapSize(0), - d_hasSafeAssignment(), - d_assignment(), +ArithVariables::ArithVariables(context::Context* c, DeltaComputeCallback deltaComputingFunc) + : d_vars(), d_safeAssignment(), - d_ubc(c), - d_lbc(c), + d_numberOfVariables(0), + d_pool(), + d_released(), + d_releasedIterator(d_released.begin()), + d_nodeToArithVarMap(), + d_lbRevertHistory(c, true, LowerBoundCleanUp(this)), + d_ubRevertHistory(c, true, UpperBoundCleanUp(this)), d_deltaIsSafe(false), d_delta(-1,1), d_deltaComputingFunc(deltaComputingFunc), - d_history() + d_enqueueingBoundCounts(true) { } +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_node(Node::null()), + d_slack(false) +{ } + +void ArithVariables::VarInfo::initialize(ArithVar v, Node n, bool slack){ + 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_slack = slack; + + if(d_slack){ + //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() ? ATInteger : ATReal; + }else{ + d_type = nodeToArithType(n); + } + + Assert(initialized()); +} +void ArithVariables::VarInfo::uninitialize(){ + d_var = ARITHVAR_SENTINEL; + d_node = Node::null(); +} + +bool ArithVariables::VarInfo::setAssignment(const DeltaRational& a, BoundCounts & 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 = boundCounts(); + } + + d_cmpAssignmentUB = cmpUB; + d_cmpAssignmentLB = cmpLB; + return lbChanged || ubChanged; +} + +void ArithVariables::releaseArithVar(ArithVar v){ + VarInfo& vi = d_vars.get(v); + 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(Constraint ub, BoundCounts& prev){ + Assert(initialized()); + d_ub = ub; + int cmpUB = (d_ub == NullConstraint) ? -1 : d_assignment.cmp(d_ub->getValue()); + bool ubChanged = cmpUB != d_cmpAssignmentUB && + (cmpUB == 0 || d_cmpAssignmentUB == 0); + if(ubChanged){ + prev = boundCounts(); + } + d_cmpAssignmentUB = cmpUB; + return ubChanged; +} -const Rational& ArithPartialModel::getDelta(){ +bool ArithVariables::VarInfo::setLowerBound(Constraint lb, BoundCounts& prev){ + Assert(initialized()); + d_lb = lb; + int cmpLB = (d_lb == NullConstraint) ? 1 : d_assignment.cmp(d_lb->getValue()); + + bool lbChanged = cmpLB != d_cmpAssignmentLB && + (cmpLB == 0 || d_cmpAssignmentLB == 0); + if(lbChanged){ + prev = boundCounts(); + } + d_cmpAssignmentLB = cmpLB; + return lbChanged; +} + +void ArithVariables::attemptToReclaimReleased(){ + std::list::iterator i_end = d_released.end(); + for(int iter = 0; iter < 20 && d_releasedIterator != i_end; ++d_releasedIterator){ + ArithVar v = *d_releasedIterator; + VarInfo& vi = d_vars.get(v); + if(vi.canBeReclaimed()){ + d_pool.push_back(v); + std::list::iterator curr = d_releasedIterator; + ++d_releasedIterator; + d_released.erase(curr); + }else{ + ++d_releasedIterator; + } + } + if(d_releasedIterator == i_end){ + d_releasedIterator = d_released.begin(); + } +} + +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); @@ -49,7 +193,7 @@ const Rational& ArithPartialModel::getDelta(){ return d_delta; } -bool ArithPartialModel::boundsAreEqual(ArithVar x) const{ +bool ArithVariables::boundsAreEqual(ArithVar x) const{ if(hasLowerBound(x) && hasUpperBound(x)){ return getUpperBound(x) == getLowerBound(x); }else{ @@ -57,102 +201,108 @@ bool ArithPartialModel::boundsAreEqual(ArithVar x) const{ } } -void ArithPartialModel::setAssignment(ArithVar x, const DeltaRational& r){ - Debug("partial_model") << "pm: updating the assignment to" << x - << " now " << r <getValue(); } -const DeltaRational& ArithPartialModel::getLowerBound(ArithVar x) const { +const DeltaRational& ArithVariables::getLowerBound(ArithVar x) const { Assert(inMaps(x)); Assert(hasLowerBound(x)); return getLowerBoundConstraint(x)->getValue(); } -const DeltaRational& ArithPartialModel::getSafeAssignment(ArithVar x) const{ +const DeltaRational& ArithVariables::getSafeAssignment(ArithVar x) const{ Assert(inMaps(x)); - if(d_hasSafeAssignment[x]){ + if(d_safeAssignment.isKey(x)){ return d_safeAssignment[x]; }else{ - return d_assignment[x]; + return d_vars[x].d_assignment; } } -const DeltaRational& ArithPartialModel::getAssignment(ArithVar x, bool safe) const{ +const DeltaRational& ArithVariables::getAssignment(ArithVar x, bool safe) const{ Assert(inMaps(x)); - if(safe && d_hasSafeAssignment[x]){ + if(safe && d_safeAssignment.isKey(x)){ return d_safeAssignment[x]; }else{ - return d_assignment[x]; + return d_vars[x].d_assignment; } } -const DeltaRational& ArithPartialModel::getAssignment(ArithVar x) const{ +const DeltaRational& ArithVariables::getAssignment(ArithVar x) const{ Assert(inMaps(x)); - return d_assignment[x]; + return d_vars[x].d_assignment; } -void ArithPartialModel::setLowerBoundConstraint(Constraint c){ +void ArithVariables::setLowerBoundConstraint(Constraint 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."); @@ -162,10 +312,15 @@ void ArithPartialModel::setLowerBoundConstraint(Constraint c){ Assert(greaterThanLowerBound(x, c->getValue())); invalidateDelta(); - d_lbc.set(x, c); + VarInfo& vi = d_vars.get(x); + pushLowerBound(vi); + BoundCounts prev; + if(vi.setLowerBound(c, prev)){ + addToBoundQueue(x, prev); + } } -void ArithPartialModel::setUpperBoundConstraint(Constraint c){ +void ArithVariables::setUpperBoundConstraint(Constraint 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."); @@ -176,29 +331,40 @@ void ArithPartialModel::setUpperBoundConstraint(Constraint c){ Assert(lessThanUpperBound(x, c->getValue())); invalidateDelta(); - d_ubc.set(x, c); + VarInfo& vi = d_vars.get(x); + pushUpperBound(vi); + BoundCounts prev; + if(vi.setUpperBound(c, prev)){ + addToBoundQueue(x, prev); + } } -void ArithPartialModel::forceRelaxLowerBound(ArithVar v){ - AssertArgument(inMaps(v), "Calling forceRelaxLowerBound on a variable that is not properly setup."); - AssertArgument(hasLowerBound(v), "Calling forceRelaxLowerBound on a variable without a lowerbound."); +// void ArithVariables::forceRelaxLowerBound(ArithVar v){ +// AssertArgument(inMaps(v), "Calling forceRelaxLowerBound on a variable that is not properly setup."); +// AssertArgument(hasLowerBound(v), "Calling forceRelaxLowerBound on a variable without a lowerbound."); - Debug("partial_model") << "forceRelaxLowerBound(" << v << ") dropping :" << getLowerBoundConstraint(v) << endl; +// Debug("partial_model") << "forceRelaxLowerBound(" << v << ") dropping :" << getLowerBoundConstraint(v) << endl; - d_lbc.set(v, NullConstraint); -} +// invalidateDelta(); +// VarInfo& vi = d_vars.get(v); +// pushLowerBound(vi); +// vi.setLowerBound(NullConstraint); +// } -void ArithPartialModel::forceRelaxUpperBound(ArithVar v){ - AssertArgument(inMaps(v), "Calling forceRelaxUpperBound on a variable that is not properly setup."); - AssertArgument(hasUpperBound(v), "Calling forceRelaxUpperBound on a variable without an upper bound."); +// void ArithVariables::forceRelaxUpperBound(ArithVar v){ +// AssertArgument(inMaps(v), "Calling forceRelaxUpperBound on a variable that is not properly setup."); +// AssertArgument(hasUpperBound(v), "Calling forceRelaxUpperBound on a variable without an upper bound."); - Debug("partial_model") << "forceRelaxUpperBound(" << v << ") dropping :" << getUpperBoundConstraint(v) << endl; +// Debug("partial_model") << "forceRelaxUpperBound(" << v << ") dropping :" << getUpperBoundConstraint(v) << endl; - d_ubc.set(v, NullConstraint); -} +// invalidateDelta(); +// VarInfo& vi = d_vars.get(v); +// pushUpperBound(vi); +// vi.setUpperBound(NullConstraint); +// } -int ArithPartialModel::cmpToLowerBound(ArithVar x, const DeltaRational& c) const{ +int ArithVariables::cmpToLowerBound(ArithVar x, const DeltaRational& c) const{ if(!hasLowerBound(x)){ // l = -\intfy // ? c < -\infty |- _|_ @@ -208,7 +374,7 @@ int ArithPartialModel::cmpToLowerBound(ArithVar x, const DeltaRational& c) const } } -int ArithPartialModel::cmpToUpperBound(ArithVar x, const DeltaRational& c) const{ +int ArithVariables::cmpToUpperBound(ArithVar x, const DeltaRational& c) const{ if(!hasUpperBound(x)){ //u = \intfy // ? c > \infty |- _|_ @@ -218,14 +384,14 @@ int ArithPartialModel::cmpToUpperBound(ArithVar x, const DeltaRational& c) const } } -bool ArithPartialModel::equalsLowerBound(ArithVar x, const DeltaRational& c){ +bool ArithVariables::equalsLowerBound(ArithVar x, const DeltaRational& c){ if(!hasLowerBound(x)){ return false; }else{ return c == getLowerBound(x); } } -bool ArithPartialModel::equalsUpperBound(ArithVar x, const DeltaRational& c){ +bool ArithVariables::equalsUpperBound(ArithVar x, const DeltaRational& c){ if(!hasUpperBound(x)){ return false; }else{ @@ -233,111 +399,210 @@ bool ArithPartialModel::equalsUpperBound(ArithVar x, const DeltaRational& c){ } } -bool ArithPartialModel::hasEitherBound(ArithVar x) const{ +bool ArithVariables::hasEitherBound(ArithVar x) const{ return hasLowerBound(x) || hasUpperBound(x); } -bool ArithPartialModel::strictlyBelowUpperBound(ArithVar x) const{ - Assert(inMaps(x)); - if(!hasUpperBound(x)){ // u = \infty - return true; - }else{ - return d_assignment[x] < getUpperBound(x); - } +bool ArithVariables::strictlyBelowUpperBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentUB < 0; + // if(!hasUpperBound(x)){ // u = \infty + // return true; + // }else{ + // return d_assignment[x] < getUpperBound(x); + // } } -bool ArithPartialModel::strictlyAboveLowerBound(ArithVar x) const{ - Assert(inMaps(x)); - if(!hasLowerBound(x)){ // l = -\infty - return true; - }else{ - return getLowerBound(x) < d_assignment[x]; - } +bool ArithVariables::strictlyAboveLowerBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentLB > 0; + // if(!hasLowerBound(x)){ // l = -\infty + // return true; + // }else{ + // return getLowerBound(x) < d_assignment[x]; + // } } -bool ArithPartialModel::assignmentIsConsistent(ArithVar x) const{ - const DeltaRational& beta = getAssignment(x); +bool ArithVariables::assignmentIsConsistent(ArithVar x) const{ + return + d_vars[x].d_cmpAssignmentLB >= 0 && + d_vars[x].d_cmpAssignmentUB <= 0; + // const DeltaRational& beta = getAssignment(x); - //l_i <= beta(x_i) <= u_i - return greaterThanLowerBound(x,beta) && lessThanUpperBound(x,beta); + // //l_i <= beta(x_i) <= u_i + // return greaterThanLowerBound(x,beta) && lessThanUpperBound(x,beta); } -void ArithPartialModel::clearSafeAssignments(bool revert){ +void ArithVariables::clearSafeAssignments(bool revert){ - for(HistoryList::iterator i = d_history.begin(); i != d_history.end(); ++i){ - ArithVar x = *i; - Assert(d_hasSafeAssignment[x]); - d_hasSafeAssignment[x] = false; + if(revert && !d_safeAssignment.empty()){ + invalidateDelta(); + } + while(!d_safeAssignment.empty()){ + ArithVar atBack = d_safeAssignment.back(); if(revert){ - d_assignment[x] = d_safeAssignment[x]; + VarInfo& vi = d_vars.get(atBack); + BoundCounts prev; + if(vi.setAssignment(d_safeAssignment[atBack], prev)){ + addToBoundQueue(atBack, prev); + } } + d_safeAssignment.pop_back(); } - - if(revert && !d_history.empty()){ - invalidateDelta(); - } - - d_history.clear(); } -void ArithPartialModel::revertAssignmentChanges(){ +void ArithVariables::revertAssignmentChanges(){ clearSafeAssignments(true); } -void ArithPartialModel::commitAssignmentChanges(){ +void ArithVariables::commitAssignmentChanges(){ clearSafeAssignments(false); } -void ArithPartialModel::printModel(ArithVar x){ - Debug("model") << "model" << x << ":"<< getAssignment(x) << " "; +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)){ - Debug("model") << "no lb "; + out << "no lb "; }else{ - Debug("model") << getLowerBound(x) << " "; - Debug("model") << getLowerBoundConstraint(x) << " "; + out << getLowerBound(x) << " "; + out << getLowerBoundConstraint(x) << " "; } if(!hasUpperBound(x)){ - Debug("model") << "no ub "; + out << "no ub "; }else{ - Debug("model") << getUpperBound(x) << " "; - Debug("model") << getUpperBoundConstraint(x) << " "; + out << getUpperBound(x) << " "; + out << getUpperBoundConstraint(x) << " "; } - Debug("model") << endl; + out << endl; } -// void ArithPartialModel::deltaIsSmallerThan(const DeltaRational& l, const DeltaRational& u){ -// const Rational& c = l.getNoninfinitesimalPart(); -// const Rational& k = l.getInfinitesimalPart(); -// const Rational& d = u.getNoninfinitesimalPart(); -// const Rational& h = u.getInfinitesimalPart(); +void ArithVariables::printModel(ArithVar x) const{ + printModel(x, Debug("model")); +} -// if(c < d && k > h){ -// Rational ep = (d-c)/(k-h); -// if(ep < d_delta){ -// d_delta = ep; +// BoundRelationship ArithVariables::boundRelationship(Constraint lb, const DeltaRational& d, Constraint ub){ +// if(lb == NullConstraint && ub == NullConstraint){ +// return BetweenBounds; +// }else if(lb == NullConstraint){ +// int cmp = d.cmp(ub->getValue()); +// return (cmp < 0) ? BetweenBounds : +// (cmp == 0 ? AtUpperBound : AboveUpperBound); +// }else if(ub == NullConstraint){ +// int cmp = d.cmp(lb->getValue()); +// return (cmp > 0) ? BetweenBounds : +// (cmp == 0 ? AtLowerBound : BelowLowerBound); +// }else{ +// Assert(lb->getValue() <= ub->getValue()); +// int cmpToLB = d.cmp(lb->getValue()); +// if(cmpToLB < 0){ +// return BelowLowerBound; +// }else if(cmpToLB == 0){ +// return (d == ub->getValue()) ? AtBothBounds : AtLowerBound; +// }else{ +// // d > 0 +// int cmpToUB = d.cmp(ub->getValue()); +// return (cmpToUB > 0) ? BetweenBounds : +// (cmpToUB == 0 ? AtLowerBound : BelowLowerBound); // } // } // } -// void ArithPartialModel::computeDelta(const Rational& init){ -// Assert(!d_deltaIsSafe); -// d_delta = init; +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); + BoundCounts 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); + BoundCounts prev; + if(vi.setLowerBound(c->second, prev)){ + addToBoundQueue(x, prev); + } + --vi.d_pushCount; +} -// for(ArithVar x = 0; x < d_mapSize; ++x){ -// const DeltaRational& a = getAssignment(x); -// if(hasLowerBound(x)){ -// const DeltaRational& l = getLowerBound(x); -// deltaIsSmallerThan(l,a); +/* To ensure that the final deallocation stuff works, + * we need to ensure that we need to not reference any of the other vectors + */ +// void ArithVariables::relaxUpperBound(Constraint curr, Constraint afterPop){ +// BoundRelation next = Undefined; +// switch(d_boundRel[x]){ +// case BelowLowerBound: +// case BetweenBounds: +// case AtLowerBound: +// return; // do nothing +// case AtUpperBound: +// if(afterPop != NullConstraint +// && curr->getValue() == afterPop->getValue()){ +// next = AtUpperBound; +// }else{ +// next = BetweenBounds; +// } +// break; +// case AtBothBounds: +// if(afterPop != NullConstraint +// && curr->getValue() == afterPop->getValue()){ +// next = AtUpperBound; +// }else{ +// next = AtLowerBound; // } -// if(hasUpperBound(x)){ -// const DeltaRational& u = getUpperBound(x); -// deltaIsSmallerThan(a,u); +// break; +// case AboveUpperBound: +// if(afterPop == NullConstraint){ +// next = BetweenBounds; +// }else{ +// int cmp = d_assignment[x].cmp(afterPop->getValue()); +// next = (cmp < 0) ? BetweenBounds : +// (cmp == 0) ? AtUpperBound : AboveUpperBound; // } +// break; +// default: +// Unreachable(); // } -// d_deltaIsSafe = true; +// d_boundRel[x] = next; // } + + +// void ArithVariables::relaxLowerBound(Constraint curr, Constraint afterPop){ +// // TODO this can be optimized using the automata induced by d_boundRel and +// // the knowledge that lb <= ub +// ArithVar x = curr->getVariable(); +// d_boundRel[x] = boundRelationship(afterPop, d_assignment[x], d_ubc[x]); +// } + +void ArithVariables::LowerBoundCleanUp::operator()(AVCPair* p){ + d_pm->popLowerBound(p); +} + +void ArithVariables::UpperBoundCleanUp::operator()(AVCPair* p){ + d_pm->popUpperBound(p); +} + }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/arith/partial_model.h b/src/theory/arith/partial_model.h index 820b1b909..dcfe97079 100644 --- a/src/theory/arith/partial_model.h +++ b/src/theory/arith/partial_model.h @@ -20,53 +20,216 @@ #include "expr/node.h" #include "context/context.h" -#include "context/cdvector.h" -#include "context/cdo.h" +#include "context/cdlist.h" + #include "theory/arith/arithvar.h" +#include "theory/arith/arith_utilities.h" #include "theory/arith/delta_rational.h" #include "theory/arith/constraint_forward.h" +#include "theory/arith/callbacks.h" +#include "theory/arith/bound_counts.h" #include +#include -#ifndef __CVC4__THEORY__ARITH__PARTIAL_MODEL_H -#define __CVC4__THEORY__ARITH__PARTIAL_MODEL_H +#pragma once namespace CVC4 { namespace theory { namespace arith { -class ArithPartialModel { + + +class ArithVariables { private: + class VarInfo { + friend class ArithVariables; + ArithVar d_var; + + DeltaRational d_assignment; + Constraint d_lb; + Constraint d_ub; + int d_cmpAssignmentLB; + int d_cmpAssignmentUB; + + unsigned d_pushCount; + ArithType d_type; + Node d_node; + bool d_slack; + + public: + VarInfo(); + + bool setAssignment(const DeltaRational& r, BoundCounts& prev); + bool setLowerBound(Constraint c, BoundCounts& prev); + bool setUpperBound(Constraint c, BoundCounts& prev); + + bool initialized() const { + return d_var != ARITHVAR_SENTINEL; + } + void initialize(ArithVar v, Node n, bool slack); + void uninitialize(); + + bool canBeReclaimed() const{ + return d_pushCount == 0; + } + + BoundCounts boundCounts() const { + uint32_t lbIndc = (d_cmpAssignmentLB == 0) ? 1 : 0; + uint32_t ubIndc = (d_cmpAssignmentUB == 0) ? 1 : 0; + return BoundCounts(lbIndc, ubIndc); + } + }; + + //Maps from ArithVar -> VarInfo + typedef DenseMap VarInfoVec; + 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::list d_released; + std::list::iterator d_releasedIterator; + + // Reverse Map from Node to ArithVar + // Inverse of d_vars[x].d_node + NodeToArithVarMap d_nodeToArithVarMap; + + DenseMap d_atBoundsQueue; + + public: + + inline ArithVar getNumberOfVariables() const { + return d_numberOfVariables; + } + + inline bool hasArithVar(TNode x) const { + return d_nodeToArithVarMap.find(x) != d_nodeToArithVarMap.end(); + } - unsigned d_mapSize; + inline bool hasNode(ArithVar a) const { + return d_vars.isKey(a); + } - //Maps from ArithVar -> T - std::vector d_hasSafeAssignment; - std::vector d_assignment; - std::vector d_safeAssignment; + inline ArithVar asArithVar(TNode x) const{ + Assert(hasArithVar(x)); + Assert((d_nodeToArithVarMap.find(x))->second <= ARITHVAR_SENTINEL); + return (d_nodeToArithVarMap.find(x))->second; + } - context::CDVector d_ubc; - context::CDVector d_lbc; + + inline Node asNode(ArithVar a) const{ + Assert(hasNode(a)); + return d_vars[a].d_node; + } + + 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) + : d_vars(vars), d_wrapped(ci) + { + nextInitialized(); + } + + var_iterator& operator++(){ + ++d_wrapped; + nextInitialized(); + return *this; + } + bool operator==(const var_iterator& other) const{ + return d_wrapped == other.d_wrapped; + } + bool operator!=(const var_iterator& other) const{ + return d_wrapped != other.d_wrapped; + } + ArithVar operator*() const{ + return *d_wrapped; + } + private: + void nextInitialized(){ + VarInfoVec::const_iterator end = d_vars->end(); + while(d_wrapped != end && + !((*d_vars)[*d_wrapped].initialized())){ + ++d_wrapped; + } + } + }; + var_iterator var_begin() const { + return var_iterator(&d_vars, d_vars.begin()); + } + + var_iterator var_end() const { + return var_iterator(&d_vars, d_vars.end()); + } + + + bool canBeReleased(ArithVar v) const; + void releaseArithVar(ArithVar v); + void attemptToReclaimReleased(); + + bool isInteger(ArithVar x) const { + return d_vars[x].d_type >= ATInteger; + } + bool isSlack(ArithVar x) const { + return d_vars[x].d_slack; + } + private: + + typedef std::pair AVCPair; + class LowerBoundCleanUp { + private: + ArithVariables* d_pm; + public: + LowerBoundCleanUp(ArithVariables* pm) : d_pm(pm) {} + void operator()(AVCPair* restore); + }; + + class UpperBoundCleanUp { + private: + ArithVariables* d_pm; + public: + UpperBoundCleanUp(ArithVariables* pm) : d_pm(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. - RationalCallBack& d_deltaComputingFunc; - - /** - * List contains all of the variables that have an unsafe assignment. - */ - typedef std::vector HistoryList; - HistoryList d_history; + DeltaComputeCallback d_deltaComputingFunc; + bool d_enqueueingBoundCounts; public: - ArithPartialModel(context::Context* c, RationalCallBack& deltaComputation); + ArithVariables(context::Context* c, DeltaComputeCallback deltaComputation); /** * This sets the lower bound for a variable in the current context. @@ -82,11 +245,11 @@ public: /** Returns the constraint for the upper bound of a variable. */ inline Constraint getUpperBoundConstraint(ArithVar x) const{ - return d_ubc[x]; + return d_vars[x].d_ub; } /** Returns the constraint for the lower bound of a variable. */ inline Constraint getLowerBoundConstraint(ArithVar x) const{ - return d_lbc[x]; + return d_vars[x].d_lb; } /** @@ -94,17 +257,20 @@ public: * This is done by forcing the lower bound to be NullConstraint. * This is an expert only operation! (See primal simplex for an example.) */ - void forceRelaxLowerBound(ArithVar x); + //void forceRelaxLowerBound(ArithVar x); /** * This forces the upper bound for a variable to be relaxed in the current context. * This is done by forcing the upper bound to be NullConstraint. * This is an expert only operation! (See primal simplex for an example.) */ - void forceRelaxUpperBound(ArithVar x); + //void forceRelaxUpperBound(ArithVar x); /* Initializes a variable to a safe value.*/ - void initialize(ArithVar x, const DeltaRational& r); + //void initialize(ArithVar x, const DeltaRational& r); + void initialize(ArithVar x, Node n, bool slack); + + ArithVar allocate(Node n, bool slack = false); /* Gets the last assignment to a variable that is known to be consistent. */ const DeltaRational& getSafeAssignment(ArithVar x) const; @@ -187,20 +353,31 @@ public: 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 boundCounts(ArithVar x) const { + return d_vars[x].boundCounts(); + } bool strictlyBelowUpperBound(ArithVar x) const; bool strictlyAboveLowerBound(ArithVar x) const; bool assignmentIsConsistent(ArithVar x) const; - void printModel(ArithVar x); + 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_lbc[x] != NullConstraint; + return d_vars[x].d_lb != NullConstraint; } inline bool hasUpperBound(ArithVar x) const{ - return d_ubc[x] != NullConstraint; + return d_vars[x].d_ub != NullConstraint; } const Rational& getDelta(); @@ -214,6 +391,41 @@ public: d_deltaIsSafe = true; } + // inline bool initialized(ArithVar x) const { + // return d_vars[x].initialized(); + // } + + void addToBoundQueue(ArithVar v, BoundCounts prev){ + if(d_enqueueingBoundCounts && !d_atBoundsQueue.isKey(v)){ + d_atBoundsQueue.set(v, prev); + } + } + + BoundCounts oldBoundCounts(ArithVar v) const { + if(d_atBoundsQueue.isKey(v)){ + return d_atBoundsQueue[v]; + }else{ + return boundCounts(v); + } + } + + void startQueueingAtBoundQueue(){ d_enqueueingBoundCounts = true; } + void stopQueueingAtBoundQueue(){ d_enqueueingBoundCounts = false; } + + void processAtBoundQueue(BoundCountsCallback& changed){ + while(!d_atBoundsQueue.empty()){ + ArithVar v = d_atBoundsQueue.back(); + BoundCounts prev = d_atBoundsQueue[v]; + d_atBoundsQueue.pop_back(); + BoundCounts curr = boundCounts(v); + if(prev != curr){ + changed(v, prev); + } + } + } + + void printEntireModel(std::ostream& out) const; + private: /** @@ -222,19 +434,16 @@ private: */ void clearSafeAssignments(bool revert); - bool equalSizes(); + bool debugEqualSizes(); bool inMaps(ArithVar x) const{ - return x < d_mapSize; + return x < getNumberOfVariables(); } -};/* class ArithPartialModel */ +};/* class ArithVariables */ }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ - - -#endif /* __CVC4__THEORY__ARITH__PARTIAL_MODEL_H */ diff --git a/src/theory/arith/pure_update_simplex.cpp b/src/theory/arith/pure_update_simplex.cpp new file mode 100644 index 000000000..9b3edfa6f --- /dev/null +++ b/src/theory/arith/pure_update_simplex.cpp @@ -0,0 +1,261 @@ +/********************* */ +/*! \file simplex.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/pure_update_simplex.h" +#include "theory/arith/options.h" +#include "theory/arith/constraint.h" + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + +PureUpdateSimplexDecisionProcedure::PureUpdateSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(linEq, errors, conflictChannel, tvmalloc) +{ } + +Result::Sat PureUpdateSimplexDecisionProcedure::findModel(bool exactResult){ + Assert(d_conflictVariables.empty()); + + static CVC4_THREADLOCAL(unsigned int) instance = 0; + instance = instance + 1; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Debug("arith::findModel") << "puFindModel("<< instance <<") " + << "trivial" << endl; + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + d_errorSet.setSelectionRule(VAR_ORDER); + + if(processSignals()){ + d_conflictVariables.purge(); + + Debug("arith::findModel") << "puFindModel("<< instance <<") " + << "early conflict" << endl; + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Debug("arith::findModel") << "puFindModel("<< instance <<") " + << "fixed itself" << endl; + Assert(!d_errorSet.moreSignals()); + return Result::SAT; + } + + Debug("arith::findModel") << "puFindModel(" << instance <<") " + << "start non-trivial" << endl; + + static const bool verbose = false; + Result::Sat result = Result::SAT_UNKNOWN; + + if(result == Result::SAT_UNKNOWN){ + if(attemptPureUpdates()){ + result = Result::UNSAT; + } + if(result == Result::UNSAT){ + ++(d_statistics.d_pureUpdateFoundUnsat); + if(verbose){ Message() << "pure updates found unsat" << endl; } + }else if(d_errorSet.errorEmpty()){ + ++(d_statistics.d_pureUpdateFoundSat); + if(verbose){ Message() << "pure updates found model" << endl; } + }else{ + ++(d_statistics.d_pureUpdateMissed); + if(verbose){ Message() << "pure updates missed" << endl; } + } + } + + Assert(!d_errorSet.moreSignals()); + if(result == Result::SAT_UNKNOWN && d_errorSet.errorEmpty()){ + result = Result::SAT; + } + + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Assert(d_focusErrorVar == ARITHVAR_SENTINEL); + Debug("arith::findModel") << "end findModel() " << instance << " " + << result << endl; + + return result; +} + + + +PureUpdateSimplexDecisionProcedure::Statistics::Statistics(): + d_pureUpdateFoundUnsat("theory::arith::PureUpdate::FoundUnsat", 0), + d_pureUpdateFoundSat("theory::arith::PureUpdate::FoundSat", 0), + d_pureUpdateMissed("theory::arith::PureUpdate::Missed", 0), + d_pureUpdates("theory::arith::PureUpdate::updates", 0), + d_pureUpdateDropped("theory::arith::PureUpdate::dropped", 0), + d_pureUpdateConflicts("theory::arith::PureUpdate::conflicts", 0), + d_foundConflicts("theory::arith::PureUpdate::foundConflicts", 0), + d_attemptPureUpdatesTimer("theory::arith::PureUpdate::timer"), + d_processSignalsTime("theory::arith::PureUpdate::process::timer"), + d_constructionTimer("theory::arith::PureUpdate::construction::timer") +{ + StatisticsRegistry::registerStat(&d_pureUpdateFoundUnsat); + StatisticsRegistry::registerStat(&d_pureUpdateFoundSat); + StatisticsRegistry::registerStat(&d_pureUpdateMissed); + StatisticsRegistry::registerStat(&d_pureUpdates); + StatisticsRegistry::registerStat(&d_pureUpdateDropped); + StatisticsRegistry::registerStat(&d_pureUpdateConflicts); + + StatisticsRegistry::registerStat(&d_foundConflicts); + + StatisticsRegistry::registerStat(&d_attemptPureUpdatesTimer); + StatisticsRegistry::registerStat(&d_processSignalsTime); + StatisticsRegistry::registerStat(&d_constructionTimer); +} + +PureUpdateSimplexDecisionProcedure::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_pureUpdateFoundUnsat); + StatisticsRegistry::unregisterStat(&d_pureUpdateFoundSat); + StatisticsRegistry::unregisterStat(&d_pureUpdateMissed); + StatisticsRegistry::unregisterStat(&d_pureUpdates); + StatisticsRegistry::unregisterStat(&d_pureUpdateDropped); + StatisticsRegistry::unregisterStat(&d_pureUpdateConflicts); + + StatisticsRegistry::unregisterStat(&d_foundConflicts); + + StatisticsRegistry::unregisterStat(&d_attemptPureUpdatesTimer); + StatisticsRegistry::unregisterStat(&d_processSignalsTime); + StatisticsRegistry::unregisterStat(&d_constructionTimer); +} + +bool PureUpdateSimplexDecisionProcedure::attemptPureUpdates(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_attemptPureUpdatesTimer); + + Assert(!d_errorSet.focusEmpty()); + Assert(d_errorSet.noSignals()); + + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_constructionTimer); + + UpdateInfo proposal; + int boundImprovements = 0; + int dropped = 0; + int computations = 0; + + for( Tableau::RowIterator ri = d_tableau.basicRowIterator(d_focusErrorVar); !ri.atEnd(); ++ri){ + const Tableau::Entry& e = *ri; + + ArithVar curr = e.getColVar(); + if(curr == d_focusErrorVar){ continue; } + + int dir = e.getCoefficient().sgn(); + Assert(dir != 0); + + bool worthwhile = false; + + if( (dir > 0 && d_variables.cmpAssignmentUpperBound(curr) < 0) || + (dir < 0 && d_variables.cmpAssignmentLowerBound(curr) > 0) ){ + + ++computations; + proposal = UpdateInfo(curr, dir); + d_linEq.computeSafeUpdate(proposal, &LinearEqualityModule::noPreference); + + Assert(proposal.errorsChange() <= 0); + Assert(proposal.focusDirection() >= 0); + + worthwhile = proposal.errorsChange() < 0 || + (proposal.focusDirection() > 0 && + d_variables.boundCounts(curr).isZero() && + !proposal.describesPivot()); + + Debug("pu::refined") + << "pure update proposal " + << curr << " " + << worthwhile << " " + << proposal + << endl; + } + if(worthwhile){ + Debug("pu") << d_variables.getAssignment(d_focusErrorVar) << endl; + + BoundCounts before = d_variables.boundCounts(curr); + DeltaRational newAssignment = + d_variables.getAssignment(curr) + proposal.nonbasicDelta(); + d_linEq.updateTracked(curr, newAssignment); + BoundCounts after = d_variables.boundCounts(curr); + + ++d_statistics.d_pureUpdates; + ++boundImprovements; + Debug("pu") << boundImprovements << ": " << curr + << " before: " << before + << " after: " << after + << e.getCoefficient() + << d_variables.getAssignment(d_focusErrorVar) << endl; + + uint32_t prevSize = d_errorSet.errorSize(); + Assert(d_errorSet.moreSignals()); + if(Debug.isOn("pu")){ d_errorSet.debugPrint(Debug("pu")); } + while(d_errorSet.moreSignals()){ + ArithVar updated = d_errorSet.topSignal(); + bool wasInError = d_errorSet.inError(updated); + d_errorSet.popSignal(); + if(updated == curr){ continue; } + Assert(d_tableau.isBasic(updated)); + if(!d_linEq.basicIsTracked(updated)){continue;} + + + Assert(d_linEq.basicIsTracked(updated)); + Assert(wasInError || d_variables.assignmentIsConsistent(updated)); + + if(!d_variables.assignmentIsConsistent(updated) + && checkBasicForConflict(updated)){ + Assert(!d_conflictVariables.isMember(updated) ); + Debug("pu") + << "It worked? " + << d_statistics.d_pureUpdateConflicts.getData() + << " " << curr + << " " << checkBasicForConflict(updated) << endl; + reportConflict(updated); + ++(d_statistics.d_foundConflicts); + ++(d_statistics.d_pureUpdateConflicts); + } + } + if(d_conflictVariables.empty()){ + if(Debug.isOn("pu")){ d_errorSet.debugPrint(Debug("pu")); } + uint32_t currSize = d_errorSet.errorSize(); + Assert(currSize <= prevSize); + if(currSize < prevSize){ + dropped+= prevSize - currSize; + if(currSize == 0){ + break; + } + } + }else{ + break; + } + } + } + + tearDownInfeasiblityFunction(d_statistics.d_constructionTimer, d_focusErrorVar); + d_focusErrorVar = ARITHVAR_SENTINEL; + + (d_statistics.d_pureUpdateDropped) += dropped; + + Assert(d_errorSet.noSignals()); + return !d_conflictVariables.empty(); +} + + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/pure_update_simplex.h b/src/theory/arith/pure_update_simplex.h new file mode 100644 index 000000000..50b751d7b --- /dev/null +++ b/src/theory/arith/pure_update_simplex.h @@ -0,0 +1,118 @@ +/********************* */ +/*! \file simplex.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#pragma once + +#include "theory/arith/simplex.h" +#include "util/dense_map.h" +#include "util/statistics_registry.h" +#include +#include "theory/arith/arithvar.h" +#include "theory/arith/delta_rational.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +class PureUpdateSimplexDecisionProcedure : public SimplexDecisionProcedure{ +public: + PureUpdateSimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc); + + Result::Sat findModel(bool exactResult); + +private: + ArithVar d_focusErrorVar; + + bool attemptPureUpdates(); + + /** + * 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_foundConflicts; + return standardProcessSignals(timer, conflictStat); + } + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_pureUpdateFoundUnsat; + IntStat d_pureUpdateFoundSat; + IntStat d_pureUpdateMissed; + IntStat d_pureUpdates; + IntStat d_pureUpdateDropped; + IntStat d_pureUpdateConflicts; + + IntStat d_foundConflicts; + + TimerStat d_attemptPureUpdatesTimer; + TimerStat d_processSignalsTime; + + TimerStat d_constructionTimer; + + Statistics(); + ~Statistics(); + } d_statistics; +};/* class PureUpdateSimplexDecisionProcedure */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/simplex-converge.cpp b/src/theory/arith/simplex-converge.cpp new file mode 100644 index 000000000..decce3882 --- /dev/null +++ b/src/theory/arith/simplex-converge.cpp @@ -0,0 +1,1674 @@ +/********************* */ +/*! \file simplex.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): none + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009, 2010, 2011 The Analysis of Computer Systems Group (ACSys) + ** Courant Institute of Mathematical Sciences + ** New York University + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/simplex.h" +#include "theory/arith/options.h" + +using namespace std; + +using namespace CVC4; +using namespace CVC4::kind; + +using namespace CVC4::theory; +using namespace CVC4::theory::arith; + +static const bool CHECK_AFTER_PIVOT = true; + +SimplexDecisionProcedure::SimplexDecisionProcedure(LinearEqualityModule& linEq, NodeCallBack& conflictChannel, ArithVarMalloc& malloc, ConstraintDatabase& cd) : + d_conflictVariable(ARITHVAR_SENTINEL), + d_linEq(linEq), + d_partialModel(d_linEq.getPartialModel()), + d_tableau(d_linEq.getTableau()), + d_queue(d_partialModel, d_tableau), + d_numVariables(0), + d_conflictChannel(conflictChannel), + d_pivotsInRound(), + d_DELTA_ZERO(0,0), + d_arithVarMalloc(malloc), + d_constraintDatabase(cd), + d_optRow(ARITHVAR_SENTINEL), + d_negOptConstant(d_DELTA_ZERO) +{ + switch(ArithHeuristicPivotRule rule = options::arithHeuristicPivotRule()) { + case MINIMUM: + d_queue.setPivotRule(ArithPriorityQueue::MINIMUM); + break; + case BREAK_TIES: + d_queue.setPivotRule(ArithPriorityQueue::BREAK_TIES); + break; + case MAXIMUM: + d_queue.setPivotRule(ArithPriorityQueue::MAXIMUM); + break; + default: + Unhandled(rule); + } + + srand(62047); +} + +SimplexDecisionProcedure::Statistics::Statistics(): + d_statUpdateConflicts("theory::arith::UpdateConflicts", 0), + d_findConflictOnTheQueueTime("theory::arith::findConflictOnTheQueueTime"), + d_attemptBeforeDiffSearch("theory::arith::qi::BeforeDiffSearch::attempt",0), + d_successBeforeDiffSearch("theory::arith::qi::BeforeDiffSearch::success",0), + d_attemptAfterDiffSearch("theory::arith::qi::AfterDiffSearch::attempt",0), + d_successAfterDiffSearch("theory::arith::qi::AfterDiffSearch::success",0), + d_attemptDuringDiffSearch("theory::arith::qi::DuringDiffSearch::attempt",0), + d_successDuringDiffSearch("theory::arith::qi::DuringDiffSearch::success",0), + d_attemptDuringVarOrderSearch("theory::arith::qi::DuringVarOrderSearch::attempt",0), + d_successDuringVarOrderSearch("theory::arith::qi::DuringVarOrderSearch::success",0), + d_attemptAfterVarOrderSearch("theory::arith::qi::AfterVarOrderSearch::attempt",0), + d_successAfterVarOrderSearch("theory::arith::qi::AfterVarOrderSearch::success",0), + d_weakeningAttempts("theory::arith::weakening::attempts",0), + d_weakeningSuccesses("theory::arith::weakening::success",0), + d_weakenings("theory::arith::weakening::total",0), + d_weakenTime("theory::arith::weakening::time"), + d_simplexConflicts("theory::arith::simplexConflicts",0), + // primal + d_primalTimer("theory::arith::primal::overall::timer"), + d_internalTimer("theory::arith::primal::internal::timer"), + d_primalCalls("theory::arith::primal::calls",0), + d_primalSatCalls("theory::arith::primal::calls::sat",0), + d_primalUnsatCalls("theory::arith::primal::calls::unsat",0), + d_primalPivots("theory::arith::primal::pivots",0), + d_primalImprovingPivots("theory::arith::primal::pivots::improving",0), + d_primalThresholdReachedPivot("theory::arith::primal::thresholds",0), + d_primalThresholdReachedPivot_dropped("theory::arith::primal::thresholds::dropped",0), + d_primalReachedMaxPivots("theory::arith::primal::maxpivots",0), + d_primalReachedMaxPivots_contractMadeProgress("theory::arith::primal::maxpivots::contract",0), + d_primalReachedMaxPivots_checkForConflictWorked("theory::arith::primal::maxpivots::checkworked",0), + d_primalGlobalMinimum("theory::arith::primal::minimum",0), + d_primalGlobalMinimum_rowConflictWorked("theory::arith::primal::minimum::checkworked",0), + d_primalGlobalMinimum_firstHalfWasSat("theory::arith::primal::minimum::firsthalf::sat",0), + d_primalGlobalMinimum_firstHalfWasUnsat("theory::arith::primal::minimum::firsthalf::unsat",0), + d_primalGlobalMinimum_contractMadeProgress("theory::arith::primal::minimum::progress",0), + d_unboundedFound("theory::arith::primal::unbounded",0), + d_unboundedFound_drive("theory::arith::primal::unbounded::drive",0), + d_unboundedFound_dropped("theory::arith::primal::unbounded::dropped",0) +{ + StatisticsRegistry::registerStat(&d_statUpdateConflicts); + + StatisticsRegistry::registerStat(&d_findConflictOnTheQueueTime); + + StatisticsRegistry::registerStat(&d_attemptBeforeDiffSearch); + StatisticsRegistry::registerStat(&d_successBeforeDiffSearch); + StatisticsRegistry::registerStat(&d_attemptAfterDiffSearch); + StatisticsRegistry::registerStat(&d_successAfterDiffSearch); + StatisticsRegistry::registerStat(&d_attemptDuringDiffSearch); + StatisticsRegistry::registerStat(&d_successDuringDiffSearch); + StatisticsRegistry::registerStat(&d_attemptDuringVarOrderSearch); + StatisticsRegistry::registerStat(&d_successDuringVarOrderSearch); + StatisticsRegistry::registerStat(&d_attemptAfterVarOrderSearch); + StatisticsRegistry::registerStat(&d_successAfterVarOrderSearch); + + StatisticsRegistry::registerStat(&d_weakeningAttempts); + StatisticsRegistry::registerStat(&d_weakeningSuccesses); + StatisticsRegistry::registerStat(&d_weakenings); + StatisticsRegistry::registerStat(&d_weakenTime); + + StatisticsRegistry::registerStat(&d_simplexConflicts); + + //primal + StatisticsRegistry::registerStat(&d_primalTimer); + StatisticsRegistry::registerStat(&d_internalTimer); + + StatisticsRegistry::registerStat(&d_primalCalls); + StatisticsRegistry::registerStat(&d_primalSatCalls); + StatisticsRegistry::registerStat(&d_primalUnsatCalls); + + StatisticsRegistry::registerStat(&d_primalPivots); + StatisticsRegistry::registerStat(&d_primalImprovingPivots); + + StatisticsRegistry::registerStat(&d_primalThresholdReachedPivot); + StatisticsRegistry::registerStat(&d_primalThresholdReachedPivot_dropped); + + StatisticsRegistry::registerStat(&d_primalReachedMaxPivots); + StatisticsRegistry::registerStat(&d_primalReachedMaxPivots_contractMadeProgress); + StatisticsRegistry::registerStat(&d_primalReachedMaxPivots_checkForConflictWorked); + + + StatisticsRegistry::registerStat(&d_primalGlobalMinimum); + StatisticsRegistry::registerStat(&d_primalGlobalMinimum_rowConflictWorked); + StatisticsRegistry::registerStat(&d_primalGlobalMinimum_firstHalfWasSat); + StatisticsRegistry::registerStat(&d_primalGlobalMinimum_firstHalfWasUnsat); + StatisticsRegistry::registerStat(&d_primalGlobalMinimum_contractMadeProgress); + + + StatisticsRegistry::registerStat(&d_unboundedFound); + StatisticsRegistry::registerStat(&d_unboundedFound_drive); + StatisticsRegistry::registerStat(&d_unboundedFound_dropped); + +} + +SimplexDecisionProcedure::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_statUpdateConflicts); + + StatisticsRegistry::unregisterStat(&d_findConflictOnTheQueueTime); + + StatisticsRegistry::unregisterStat(&d_attemptBeforeDiffSearch); + StatisticsRegistry::unregisterStat(&d_successBeforeDiffSearch); + StatisticsRegistry::unregisterStat(&d_attemptAfterDiffSearch); + StatisticsRegistry::unregisterStat(&d_successAfterDiffSearch); + StatisticsRegistry::unregisterStat(&d_attemptDuringDiffSearch); + StatisticsRegistry::unregisterStat(&d_successDuringDiffSearch); + StatisticsRegistry::unregisterStat(&d_attemptDuringVarOrderSearch); + StatisticsRegistry::unregisterStat(&d_successDuringVarOrderSearch); + StatisticsRegistry::unregisterStat(&d_attemptAfterVarOrderSearch); + StatisticsRegistry::unregisterStat(&d_successAfterVarOrderSearch); + + StatisticsRegistry::unregisterStat(&d_weakeningAttempts); + StatisticsRegistry::unregisterStat(&d_weakeningSuccesses); + StatisticsRegistry::unregisterStat(&d_weakenings); + StatisticsRegistry::unregisterStat(&d_weakenTime); + + StatisticsRegistry::unregisterStat(&d_simplexConflicts); + + //primal + StatisticsRegistry::unregisterStat(&d_primalTimer); + StatisticsRegistry::unregisterStat(&d_internalTimer); + + StatisticsRegistry::unregisterStat(&d_primalCalls); + StatisticsRegistry::unregisterStat(&d_primalSatCalls); + StatisticsRegistry::unregisterStat(&d_primalUnsatCalls); + + StatisticsRegistry::unregisterStat(&d_primalPivots); + StatisticsRegistry::unregisterStat(&d_primalImprovingPivots); + + StatisticsRegistry::unregisterStat(&d_primalThresholdReachedPivot); + StatisticsRegistry::unregisterStat(&d_primalThresholdReachedPivot_dropped); + + StatisticsRegistry::unregisterStat(&d_primalReachedMaxPivots); + StatisticsRegistry::unregisterStat(&d_primalReachedMaxPivots_contractMadeProgress); + StatisticsRegistry::unregisterStat(&d_primalReachedMaxPivots_checkForConflictWorked); + + + StatisticsRegistry::unregisterStat(&d_primalGlobalMinimum); + StatisticsRegistry::unregisterStat(&d_primalGlobalMinimum_rowConflictWorked); + StatisticsRegistry::unregisterStat(&d_primalGlobalMinimum_firstHalfWasSat); + StatisticsRegistry::unregisterStat(&d_primalGlobalMinimum_firstHalfWasUnsat); + StatisticsRegistry::unregisterStat(&d_primalGlobalMinimum_contractMadeProgress); + + StatisticsRegistry::unregisterStat(&d_unboundedFound); + StatisticsRegistry::unregisterStat(&d_unboundedFound_drive); + StatisticsRegistry::unregisterStat(&d_unboundedFound_dropped); +} + + + + +ArithVar SimplexDecisionProcedure::minVarOrder(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + // Assert(!simp.d_tableau.isBasic(x)); + // Assert(!simp.d_tableau.isBasic(y)); + if(x <= y){ + return x; + } else { + return y; + } +} + +ArithVar SimplexDecisionProcedure::minColLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(!simp.d_tableau.isBasic(x)); + Assert(!simp.d_tableau.isBasic(y)); + uint32_t xLen = simp.d_tableau.getColLength(x); + uint32_t yLen = simp.d_tableau.getColLength(y); + if( xLen > yLen){ + return y; + } else if( xLen== yLen ){ + return minVarOrder(simp,x,y); + }else{ + return x; + } +} + +ArithVar SimplexDecisionProcedure::minRowLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(simp.d_tableau.isBasic(x)); + Assert(simp.d_tableau.isBasic(y)); + uint32_t xLen = simp.d_tableau.getRowLength(simp.d_tableau.basicToRowIndex(x)); + uint32_t yLen = simp.d_tableau.getRowLength(simp.d_tableau.basicToRowIndex(y)); + if( xLen > yLen){ + return y; + } else if( xLen == yLen ){ + return minVarOrder(simp,x,y); + }else{ + return x; + } +} +ArithVar SimplexDecisionProcedure::minBoundAndRowCount(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(!simp.d_tableau.isBasic(x)); + Assert(!simp.d_tableau.isBasic(y)); + if(simp.d_partialModel.hasEitherBound(x) && !simp.d_partialModel.hasEitherBound(y)){ + return y; + }else if(!simp.d_partialModel.hasEitherBound(x) && simp.d_partialModel.hasEitherBound(y)){ + return x; + }else { + return minColLength(simp, x, y); + } +} + +template +ArithVar SimplexDecisionProcedure::selectSlack(ArithVar x_i, SimplexDecisionProcedure::PreferenceFunction pref){ + 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 : pref(*this, slack, nonbasic); + } + } + + return slack; +} + +Node betterConflict(TNode x, TNode y){ + if(x.isNull()) return y; + else if(y.isNull()) return x; + else if(x.getNumChildren() <= y.getNumChildren()) return x; + else return y; +} + +bool SimplexDecisionProcedure::findConflictOnTheQueue(SearchPeriod type) { + TimerStat::CodeTimer codeTimer(d_statistics.d_findConflictOnTheQueueTime); + Assert(d_successes.empty()); + + switch(type){ + case BeforeDiffSearch: ++(d_statistics.d_attemptBeforeDiffSearch); break; + case DuringDiffSearch: ++(d_statistics.d_attemptDuringDiffSearch); break; + case AfterDiffSearch: ++(d_statistics.d_attemptAfterDiffSearch); break; + case DuringVarOrderSearch: ++(d_statistics.d_attemptDuringVarOrderSearch); break; + case AfterVarOrderSearch: ++(d_statistics.d_attemptAfterVarOrderSearch); break; + } + + ArithPriorityQueue::const_iterator i = d_queue.begin(); + ArithPriorityQueue::const_iterator end = d_queue.end(); + for(; i != end; ++i){ + ArithVar x_i = *i; + + if(x_i != d_conflictVariable && d_tableau.isBasic(x_i) && !d_successes.isMember(x_i)){ + Node possibleConflict = checkBasicForConflict(x_i); + if(!possibleConflict.isNull()){ + d_successes.add(x_i); + reportConflict(possibleConflict); + } + } + } + if(!d_successes.empty()){ + switch(type){ + case BeforeDiffSearch: ++(d_statistics.d_successBeforeDiffSearch); break; + case DuringDiffSearch: ++(d_statistics.d_successDuringDiffSearch); break; + case AfterDiffSearch: ++(d_statistics.d_successAfterDiffSearch); break; + case DuringVarOrderSearch: ++(d_statistics.d_successDuringVarOrderSearch); break; + case AfterVarOrderSearch: ++(d_statistics.d_successAfterVarOrderSearch); break; + } + d_successes.purge(); + return true; + }else{ + return false; + } +} + +Result::Sat SimplexDecisionProcedure::dualFindModel(bool exactResult){ + Assert(d_conflictVariable == ARITHVAR_SENTINEL); + Assert(d_queue.inCollectionMode()); + + if(d_queue.empty()){ + return Result::SAT; + } + static CVC4_THREADLOCAL(unsigned int) instance = 0; + instance = instance + 1; + Debug("arith::findModel") << "begin findModel()" << instance << endl; + + d_queue.transitionToDifferenceMode(); + + Result::Sat result = Result::SAT_UNKNOWN; + + if(d_queue.empty()){ + result = Result::SAT; + }else if(d_queue.size() > 1){ + if(findConflictOnTheQueue(BeforeDiffSearch)){ + result = Result::UNSAT; + } + } + static const bool verbose = false; + exactResult |= options::arithStandardCheckVarOrderPivots() < 0; + const uint32_t inexactResultsVarOrderPivots = exactResult ? 0 : options::arithStandardCheckVarOrderPivots(); + + uint32_t checkPeriod = options::arithSimplexCheckPeriod(); + if(result == Result::SAT_UNKNOWN){ + uint32_t numDifferencePivots = options::arithHeuristicPivots() < 0 ? + d_numVariables + 1 : options::arithHeuristicPivots(); + // The signed to unsigned conversion is safe. + uint32_t pivotsRemaining = numDifferencePivots; + while(!d_queue.empty() && + result != Result::UNSAT && + pivotsRemaining > 0){ + uint32_t pivotsToDo = min(checkPeriod, pivotsRemaining); + pivotsRemaining -= pivotsToDo; + if(searchForFeasibleSolution(pivotsToDo)){ + result = Result::UNSAT; + }//Once every CHECK_PERIOD examine the entire queue for conflicts + if(result != Result::UNSAT){ + if(findConflictOnTheQueue(DuringDiffSearch)) { result = Result::UNSAT; } + }else{ + findConflictOnTheQueue(AfterDiffSearch); // already unsat + } + } + + if(verbose && numDifferencePivots > 0){ + if(result == Result::UNSAT){ + Message() << "diff order found unsat" << endl; + }else if(d_queue.empty()){ + Message() << "diff order found model" << endl; + }else{ + Message() << "diff order missed" << endl; + } + } + } + + if(!d_queue.empty() && result != Result::UNSAT){ + if(exactResult){ + d_queue.transitionToVariableOrderMode(); + + while(!d_queue.empty() && result != Result::UNSAT){ + if(searchForFeasibleSolution(checkPeriod)){ + result = Result::UNSAT; + } + + //Once every CHECK_PERIOD examine the entire queue for conflicts + if(result != Result::UNSAT){ + if(findConflictOnTheQueue(DuringVarOrderSearch)){ + result = Result::UNSAT; + } + } else{ + findConflictOnTheQueue(AfterVarOrderSearch); + } + } + if(verbose){ + if(result == Result::UNSAT){ + Message() << "bland found unsat" << endl; + }else if(d_queue.empty()){ + Message() << "bland found model" << endl; + }else{ + Message() << "bland order missed" << endl; + } + } + }else{ + d_queue.transitionToVariableOrderMode(); + + if(searchForFeasibleSolution(inexactResultsVarOrderPivots)){ + result = Result::UNSAT; + findConflictOnTheQueue(AfterVarOrderSearch); // already unsat + }else{ + if(findConflictOnTheQueue(AfterVarOrderSearch)) { result = Result::UNSAT; } + } + + if(verbose){ + if(result == Result::UNSAT){ + Message() << "restricted var order found unsat" << endl; + }else if(d_queue.empty()){ + Message() << "restricted var order found model" << endl; + }else{ + Message() << "restricted var order missed" << endl; + } + } + } + } + + if(result == Result::SAT_UNKNOWN && d_queue.empty()){ + result = Result::SAT; + } + + + + d_pivotsInRound.purge(); + // ensure that the conflict variable is still in the queue. + if(d_conflictVariable != ARITHVAR_SENTINEL){ + d_queue.enqueueIfInconsistent(d_conflictVariable); + } + d_conflictVariable = ARITHVAR_SENTINEL; + + d_queue.transitionToCollectionMode(); + Assert(d_queue.inCollectionMode()); + Debug("arith::findModel") << "end findModel() " << instance << " " << result << endl; + return result; + + + // Assert(foundConflict || d_queue.empty()); + + // // Curiously the invariant that we always do a full check + // // means that the assignment we can always empty these queues. + // d_queue.clear(); + // d_pivotsInRound.purge(); + // d_conflictVariable = ARITHVAR_SENTINEL; + + // Assert(!d_queue.inCollectionMode()); + // d_queue.transitionToCollectionMode(); + + + // Assert(d_queue.inCollectionMode()); + + // Debug("arith::findModel") << "end findModel() " << instance << endl; + + // return foundConflict; +} + + + +Node SimplexDecisionProcedure::checkBasicForConflict(ArithVar basic){ + + Assert(d_tableau.isBasic(basic)); + const DeltaRational& beta = d_partialModel.getAssignment(basic); + + if(d_partialModel.strictlyLessThanLowerBound(basic, beta)){ + ArithVar x_j = selectSlackUpperBound(basic); + if(x_j == ARITHVAR_SENTINEL ){ + return generateConflictBelowLowerBound(basic); + } + }else if(d_partialModel.strictlyGreaterThanUpperBound(basic, beta)){ + ArithVar x_j = selectSlackLowerBound(basic); + if(x_j == ARITHVAR_SENTINEL ){ + return generateConflictAboveUpperBound(basic); + } + } + return Node::null(); +} + +//corresponds to Check() in dM06 +//template +bool SimplexDecisionProcedure::searchForFeasibleSolution(uint32_t remainingIterations){ + Debug("arith") << "searchForFeasibleSolution" << endl; + Assert(remainingIterations > 0); + + while(remainingIterations > 0){ + if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + + ArithVar x_i = d_queue.dequeueInconsistentBasicVariable(); + Debug("arith::update::select") << "selectSmallestInconsistentVar()=" << x_i << endl; + if(x_i == ARITHVAR_SENTINEL){ + Debug("arith_update") << "No inconsistent variables" << endl; + return false; //sat + } + + --remainingIterations; + + bool useVarOrderPivot = d_pivotsInRound.count(x_i) >= options::arithPivotThreshold(); + if(!useVarOrderPivot){ + d_pivotsInRound.add(x_i); + } + + + Debug("playground") << "pivots in rounds: " << d_pivotsInRound.count(x_i) + << " use " << useVarOrderPivot + << " threshold " << options::arithPivotThreshold() + << endl; + + PreferenceFunction pf = useVarOrderPivot ? minVarOrder : minBoundAndRowCount; + + DeltaRational beta_i = d_partialModel.getAssignment(x_i); + ArithVar x_j = ARITHVAR_SENTINEL; + + if(d_partialModel.strictlyLessThanLowerBound(x_i, beta_i)){ + x_j = selectSlackUpperBound(x_i, pf); + if(x_j == ARITHVAR_SENTINEL ){ + ++(d_statistics.d_statUpdateConflicts); + Node conflict = generateConflictBelowLowerBound(x_i); //unsat + d_conflictVariable = x_i; + reportConflict(conflict); + return true; + } + DeltaRational l_i = d_partialModel.getLowerBound(x_i); + d_linEq.pivotAndUpdate(x_i, x_j, l_i); + + }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, beta_i)){ + x_j = selectSlackLowerBound(x_i, pf); + if(x_j == ARITHVAR_SENTINEL ){ + ++(d_statistics.d_statUpdateConflicts); + Node conflict = generateConflictAboveUpperBound(x_i); //unsat + + d_conflictVariable = x_i; + reportConflict(conflict); + return true; + } + DeltaRational u_i = d_partialModel.getUpperBound(x_i); + d_linEq.pivotAndUpdate(x_i, x_j, u_i); + } + Assert(x_j != ARITHVAR_SENTINEL); + + //Check to see if we already have a conflict with x_j to prevent wasteful work + if(CHECK_AFTER_PIVOT){ + Node possibleConflict = checkBasicForConflict(x_j); + if(!possibleConflict.isNull()){ + d_conflictVariable = x_j; + reportConflict(possibleConflict); + return true; // unsat + } + } + } + Assert(remainingIterations == 0); + + return false; +} + + + +Constraint SimplexDecisionProcedure::weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic){ + + int sgn = coeff.sgn(); + bool ub = aboveUpper?(sgn < 0) : (sgn > 0); + + Constraint c = ub ? + d_partialModel.getUpperBoundConstraint(v) : + d_partialModel.getLowerBoundConstraint(v); + +// #warning "revisit" +// Node exp = ub ? +// d_partialModel.explainUpperBound(v) : +// d_partialModel.explainLowerBound(v); + + bool weakened; + do{ + const DeltaRational& bound = c->getValue(); + + weakened = false; + + Constraint weaker = ub? + c->getStrictlyWeakerUpperBound(true, true): + c->getStrictlyWeakerLowerBound(true, true); + + // Node weaker = ub? + // d_propManager.strictlyWeakerAssertedUpperBound(v, bound): + // d_propManager.strictlyWeakerAssertedLowerBound(v, bound); + + if(weaker != NullConstraint){ + //if(!weaker.isNull()){ + const DeltaRational& weakerBound = weaker->getValue(); + //DeltaRational weakerBound = asDeltaRational(weaker); + + 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; + + Debug("weak") << "found:" << endl; + if(v == basic){ + Debug("weak") << " basic: "; + } + Debug("weak") << " " << surplus << " "<< diff << endl + << " " << bound << c << endl + << " " << weakerBound << weaker << endl; + + Assert(diff > d_DELTA_ZERO); + c = weaker; + } + } + }while(weakened); + + return c; +} + +Node SimplexDecisionProcedure::weakenConflict(bool aboveUpper, ArithVar basicVar){ + TimerStat::CodeTimer codeTimer(d_statistics.d_weakenTime); + + const DeltaRational& assignment = d_partialModel.getAssignment(basicVar); + DeltaRational surplus; + if(aboveUpper){ + Assert(d_partialModel.hasUpperBound(basicVar)); + Assert(assignment > d_partialModel.getUpperBound(basicVar)); + surplus = assignment - d_partialModel.getUpperBound(basicVar); + }else{ + Assert(d_partialModel.hasLowerBound(basicVar)); + Assert(assignment < d_partialModel.getLowerBound(basicVar)); + surplus = d_partialModel.getLowerBound(basicVar) - assignment; + } + + NodeBuilder<> conflict(kind::AND); + 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; + Constraint c = weakestExplanation(aboveUpper, surplus, v, coeff, weakening, basicVar); + Debug("weak") << "weak : " << weakening << " " + << c->assertedToTheTheory() << " " + << d_partialModel.getAssignment(v) << " " + << c << endl + << c->explainForConflict() << endl; + anyWeakenings = anyWeakenings || weakening; + + Debug("weak") << "weak : " << c->explainForConflict() << endl; + c->explainForConflict(conflict); + } + ++d_statistics.d_weakeningAttempts; + if(anyWeakenings){ + ++d_statistics.d_weakeningSuccesses; + } + return conflict; +} + + +Node SimplexDecisionProcedure::generateConflictAboveUpperBound(ArithVar conflictVar){ + return weakenConflict(true, conflictVar); +} + +Node SimplexDecisionProcedure::generateConflictBelowLowerBound(ArithVar conflictVar){ + return weakenConflict(false, conflictVar); +} + + +// responses +// unbounded below(arithvar) +// reached threshold +// reached maxpivots +// reached GlobalMinimumd +// +SimplexDecisionProcedure::PrimalResponse SimplexDecisionProcedure::primal(uint32_t maxIterations) +{ + Assert(d_optRow != ARITHVAR_SENTINEL); + + for(uint32_t iteration = 0; iteration < maxIterations; iteration++){ + if(belowThreshold()){ + return ReachedThresholdValue; + } + + PrimalResponse res = primalCheck(); + switch(res){ + case GlobalMinimum: + case FoundUnboundedVariable: + return res; + case NoProgressOnLeaving: + ++d_statistics.d_primalPivots; + ++d_pivotsSinceOptProgress; + ++d_pivotsSinceLastCheck; + ++d_pivotsSinceErrorProgress; + + d_linEq.pivotAndUpdate(d_primalCarry.d_entering, d_primalCarry.d_leaving, d_partialModel.getAssignment(d_primalCarry.d_entering)); + + if(Debug.isOn("primal::tableau")){ + d_linEq.debugCheckTableau(); + } + if(Debug.isOn("primal::consistent")){ Assert(d_linEq.debugEntireLinEqIsConsistent("MakeProgressOnLeaving")); } + + break; + case MakeProgressOnLeaving: + { + ++d_statistics.d_primalPivots; + ++d_statistics.d_primalImprovingPivots; + + d_pivotsSinceOptProgress = 0; + ++d_pivotsSinceErrorProgress; + ++d_pivotsSinceLastCheck; + + d_linEq.pivotAndUpdate(d_primalCarry.d_entering, d_primalCarry.d_leaving, d_primalCarry.d_nextEnteringValue); + + static int helpful = 0; + static int hurtful = 0; + static int penguin = 0; + if(d_currentErrorVariables.isKey(d_primalCarry.d_entering)){ + cout << "entering is error " << d_primalCarry.d_entering; + if(d_currentErrorVariables[d_primalCarry.d_entering].errorIsLeqZero(d_partialModel)){ + cout << " now below threshold (" << (++helpful) << ") " << d_pivotsSinceErrorProgress << endl; + }else{ + cout << "ouch (" << (++hurtful) << ")"<< d_pivotsSinceErrorProgress << endl; + } + }else if(d_currentErrorVariables.isKey(d_primalCarry.d_leaving)){ + cout << "leaving is error " << d_primalCarry.d_leaving; + if(d_currentErrorVariables[d_primalCarry.d_leaving].errorIsLeqZero(d_partialModel)){ + cout << " now below threshold(" << (++helpful) << ")" << d_pivotsSinceErrorProgress << endl; + }else{ + cout << "ouch (" << (++hurtful) << ")" << d_pivotsSinceErrorProgress<< endl; + } + }else{ + cout << " penguin (" << (++penguin) << ")" << d_pivotsSinceErrorProgress<< endl; + } + + if(Debug.isOn("primal::tableau")){ + d_linEq.debugCheckTableau(); + } + if(Debug.isOn("primal::consistent")){ Assert(d_linEq.debugEntireLinEqIsConsistent("MakeProgressOnLeaving")); } + } + break; + default: + Unreachable(); + } + } + return UsedMaxPivots; +} + + +/** + * Error set + * ErrorVariable |-> {ErrorVariable, InputVariable, InputConstraint} + */ + +/** + * Returns SAT if it was able to satisfy all of the constraints in the error set + * Returns UNSAT if it was able to able to find an error + */ +Result::Sat SimplexDecisionProcedure::primalConverge(int depth){ + d_pivotsSinceLastCheck = 0; + + while(!d_currentErrorVariables.empty()){ + PrimalResponse res = primal(MAX_ITERATIONS - d_pivotsSinceLastCheck); + + switch(res){ + case FoundUnboundedVariable: + + // Drive the variable to at least 0 + // TODO This variable should be driven to a value that makes all of the error functions including it 0 + // It'll or another unbounded will be selected in the next round anyways so ignore for now. + ++d_statistics.d_unboundedFound; + if( !belowThreshold() ){ + driveOptToZero(d_primalCarry.d_unbounded); + d_linEq.debugCheckTableau(); + if(Debug.isOn("primal::consistent")){ Assert(d_linEq.debugEntireLinEqIsConsistent("primalConverge")); } + + ++d_statistics.d_unboundedFound_drive; + } + Assert(belowThreshold()); + { + uint32_t dropped = contractErrorVariables(true); + Debug("primal::converge") << "primalConverge -> FoundUnboundedVariable -> dropped " << dropped << " to " << d_currentErrorVariables.size() << endl; + d_statistics.d_unboundedFound_dropped += dropped; + } + break; + case ReachedThresholdValue: + ++d_statistics.d_primalThresholdReachedPivot; + + Assert(belowThreshold()); + { + uint32_t dropped = contractErrorVariables(true); + Debug("primal::converge") << "primalConverge -> ReachedThresholdValue -> dropped " << dropped << " to " << d_currentErrorVariables.size() << endl; + d_statistics.d_primalThresholdReachedPivot_dropped += dropped; + } + break; + case UsedMaxPivots: + { + d_pivotsSinceLastCheck = 0; + ++d_statistics.d_primalReachedMaxPivots; + + // periodically attempt to do the following : + // contract the error variable + // check for a conflict on an error variable + uint32_t dropped = contractErrorVariables(false); + + if( checkForRowConflicts() ){ // try to periodically look for a row + Debug("primal::converge") << "primalConverge -> UsedMaxPivots -> unsat " << endl; + + ++d_statistics.d_primalReachedMaxPivots_checkForConflictWorked; + return Result::UNSAT; // row conflicts are minimal so stop. + } + + if(dropped > 0){ + Debug("primal::converge") << "primalConverge -> UsedMaxPivots -> dropped " << dropped << " to " << d_currentErrorVariables.size() << endl; + ++d_statistics.d_primalReachedMaxPivots_contractMadeProgress; + }else{ + Debug("primal::converge") << "primalConverge -> UsedMaxPivots -> nothing " << endl; + } + } + break; + case GlobalMinimum: + ++d_statistics.d_primalGlobalMinimum; + + // If the minimum is positive, this is unsat. + // However, the optimization row is not necessarily a minimal conflict + if(!belowThreshold()){ + + if(d_currentErrorVariables.size() == 1){ + // The optimization function is exactly the same as the last remaining variable + // The conflict for the row is the same as the conflict for the optimization function. + bool foundConflict = checkForRowConflicts(); + Assert(foundConflict); + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> one variable" << endl; + + return Result::UNSAT; + }else{ + // There are at least 2 error variables. + // Look for a minimal conflict + + + if(checkForRowConflicts() ){ + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> postitive -> rowconflict " << endl; + + ++d_statistics.d_primalGlobalMinimum_rowConflictWorked; + return Result::UNSAT; + } + + uint32_t dropped = contractErrorVariables(false); + + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> postitive -> dropped " << dropped << " to " << d_currentErrorVariables.size() << endl; + if(dropped > 0){ + ++d_statistics.d_primalGlobalMinimum_contractMadeProgress; + } + + ErrorMap half; + d_currentErrorVariables.splitInto(half); + + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> recursion " << depth << endl; + + + reconstructOptimizationFunction(); + Result::Sat resultOnRemaining = primalConverge(depth + 1); + + if(resultOnRemaining == Result::UNSAT){ + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> recursion " << depth << " was unsat " << endl; + ++d_statistics.d_primalGlobalMinimum_firstHalfWasUnsat; + restoreErrorVariables(half); + return Result::UNSAT; + }else{ + ++d_statistics.d_primalGlobalMinimum_firstHalfWasSat; + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> recursion " << depth << " was sat " << endl; + + Assert(resultOnRemaining == Result::SAT); + Assert(d_currentErrorVariables.empty()); + d_currentErrorVariables.addAll(half); + reconstructOptimizationFunction(); + return primalConverge(depth + 1); + } + } + + }else{ + + // if the optimum is <= 0 + // drop all of the satisfied variables and continue; + uint32_t dropped = contractErrorVariables(true); + Debug("primal::converge") << "primalConverge -> GlobalMinimum -> negative -> dropped "<< dropped << " to " << d_currentErrorVariables.size() << endl; + + ++d_statistics.d_primalGlobalMinimum_contractMadeProgress; + } + break; + default: + Unreachable(); + } + } + + return Result::SAT; +} + + +Result::Sat SimplexDecisionProcedure::primalFindModel(){ + Assert(d_primalCarry.isClear()); + + // Reduce the queue to only contain violations + reduceQueue(); + + if(d_queue.empty()){ + return Result::SAT; + } + TimerStat::CodeTimer codeTimer(d_statistics.d_primalTimer); + + ++d_statistics.d_primalCalls; + + Debug("primalFindModel") << "primalFindModel() begin" << endl; + + const int PAUSE_RATE = 100; + if(Debug.isOn("primal::pause") && d_statistics.d_primalCalls.getData() % PAUSE_RATE == 0){ + Debug("primal::pause") << "waiting for input: "; + std::string dummy; + std::getline(std::cin, dummy); + } + + // TODO restore the tableau by ejecting variables + // Tableau copy(d_tableau); + + Result::Sat answer; + { + TimerStat::CodeTimer codeTimer(d_statistics.d_internalTimer); + + // This is needed because of the fiddling with the partial model + //context::Context::ScopedPush speculativePush(satContext); + + constructErrorVariables(); + constructOptimizationFunction(); + if(Debug.isOn("primal::tableau")){ d_linEq.debugCheckTableau(); } + if(Debug.isOn("primal::consistent")){ d_linEq.debugEntireLinEqIsConsistent("primalFindModel 1"); } + answer = primalConverge(0); + } + removeOptimizationFunction(); + + + // exit + uint32_t nc = d_tableau.getNumColumns(); + //d_tableau = copy; + while(d_tableau.getNumColumns() < nc){ + d_tableau.increaseSize(); + } + restoreErrorVariables(d_currentErrorVariables); + + reduceQueue(); + + if(Debug.isOn("primal::tableau")){ d_linEq.debugCheckTableau(); } + + if(Debug.isOn("primal::consistent")){ d_linEq.debugEntireLinEqIsConsistent("primalFindModel2"); } + Debug("primalFindModel") << "primalFindModel() end " << answer << endl; + + // The set of variables in conflict with their bounds will still be a subset of the + // variables that are in conflict with their bounds in the beginning. + // The basic variables are the same because of the copy. + // Thus it is safe to not try to not recompute the queue of violating variables + + if(answer == Result::UNSAT){ + // This needs to be done in a different context level than the push + ++d_statistics.d_primalUnsatCalls; + }else{ + ++d_statistics.d_primalSatCalls; + } + + d_primalCarry.clear(); + + return answer; +} + +/** Clears the ErrorMap and relase the resources associated with it. + * There are a couple of error maps around + */ +void SimplexDecisionProcedure::restoreErrorVariables(SimplexDecisionProcedure::ErrorMap& es){ + while(!es.empty()){ + ArithVar e = es.back(); + + reassertErrorConstraint(e, es, false, false); + es.pop_back(); + } +} + +void SimplexDecisionProcedure::constructErrorVariables(){ + Assert(d_currentErrorVariables.empty()); + Assert(!d_queue.empty()); + + for(ArithPriorityQueue::const_iterator iter = d_queue.begin(), end = d_queue.end(); iter != end; ++iter){ + ArithVar input = *iter; + + Assert(d_tableau.isBasic(input)); + Assert(!d_partialModel.assignmentIsConsistent(input)); + + Assert(!d_currentErrorVariables.isKey(input)); + + bool ub = d_partialModel.strictlyGreaterThanUpperBound(input, d_partialModel.getAssignment(input)); + + Constraint original = ub ? d_partialModel.getUpperBoundConstraint(input) + : d_partialModel.getLowerBoundConstraint(input); + + d_currentErrorVariables.set(input, ErrorInfo(input, ub, original)); + + if(ub){ + d_partialModel.forceRelaxUpperBound(input); + }else{ + d_partialModel.forceRelaxLowerBound(input); + } + Debug("primal") << "adding error variable (" << input << ", " << ", " << original <<") "; + Debug("primal") << "ub "<< ub << " " << d_partialModel.getAssignment(input) << " " << original->getValue() <<")" << endl; + d_currentErrorVariables.set(input, ErrorInfo(input, ub, original)); + + // Constraint boundIsValue = d_constraintDatabase.getConstraint(bound, Equality, original->getValue()); + // boundIsValue->setPsuedoConstraint(); + + // d_partialModel.setAssignment(bound, boundIsValue->getValue()); + // d_partialModel.setUpperBoundConstraint(boundIsValue); + // d_partialModel.setLowerBoundConstraint(boundIsValue); + + // // if ub + // // then error = x - boundIsValue + // // else error = boundIsValue - x + + // ArithVar error = requestVariable(); + + // DeltaRational diff = ub ? + // d_partialModel.getAssignment(input) - boundIsValue->getValue() : + // boundIsValue->getValue() - d_partialModel.getAssignment(input); + + // d_partialModel.setAssignment(error, diff); + + // vector coeffs; + // vector variables; + // variables.push_back(input); + // coeffs.push_back(ub ? Rational(1) : Rational(-1)); + // variables.push_back(bound); + // coeffs.push_back(ub ? Rational(-1) : Rational(1)); + + // d_tableau.addRow(error, coeffs, variables); + + + } + + if(Debug.isOn("primal::tableau")){ d_linEq.debugCheckTableau(); } + if(Debug.isOn("primal::consistent")){ d_linEq.debugEntireLinEqIsConsistent("constructErrorVariables");} + Assert(!d_currentErrorVariables.empty()); +} + + + +/** Returns true if it has found a row conflict for any of the error variables. */ +bool SimplexDecisionProcedure::checkForRowConflicts(){ + vector inConflict; + for(ErrorMap::const_iterator iter = d_currentErrorVariables.begin(), end = d_currentErrorVariables.end(); iter != end; ++iter){ + ArithVar error = *iter; + const ErrorInfo& info = d_currentErrorVariables[error]; + if(d_tableau.isBasic(error) && !info.errorIsLeqZero(d_partialModel)){ + + ArithVar x_j = info.isUpperbound() ? + selectSlackLowerBound(error) : + selectSlackUpperBound(error); + + if(x_j == ARITHVAR_SENTINEL ){ + inConflict.push_back(error); + } + } + } + + if(!inConflict.empty()){ + while(!inConflict.empty()){ + ArithVar error = inConflict.back(); + inConflict.pop_back(); + + reassertErrorConstraint(error, d_currentErrorVariables, false, true); + + Node conflict = d_currentErrorVariables[error].isUpperbound() ? + generateConflictAboveUpperBound(error) : + generateConflictBelowLowerBound(error); + Assert(!conflict.isNull()); + + d_currentErrorVariables.remove(error); + + reportConflict(conflict); + } + reconstructOptimizationFunction(); + return true; + }else{ + return false; + } +} + +void SimplexDecisionProcedure::reassertErrorConstraint(ArithVar e, SimplexDecisionProcedure::ErrorMap& es, bool definitelyTrue, bool definitelyFalse){ + Assert(es.isKey(e)); + const ErrorInfo& info = es.get(e); + Constraint original = info.getConstraint(); + + if(info.isUpperbound()){ + d_partialModel.setUpperBoundConstraint(original); + }else if(original->isLowerBound()){ + d_partialModel.setLowerBoundConstraint(original); + } + + Assert(!definitelyTrue || d_partialModel.assignmentIsConsistent(e)); + Assert(!definitelyFalse || !d_partialModel.assignmentIsConsistent(e)); +} + +uint32_t SimplexDecisionProcedure::contractErrorVariables(bool guaranteedSuccess){ + uint32_t entrySize = d_currentErrorVariables.size(); + Debug("primal::contract") << "contractErrorVariables() begin : " << d_currentErrorVariables.size() << endl; + + std::vector toRemove; + for(ErrorMap::const_iterator iter = d_currentErrorVariables.begin(), end = d_currentErrorVariables.end(); iter != end; ++iter){ + ArithVar e = *iter; + if(d_currentErrorVariables[e].errorIsLeqZero(d_partialModel)){ + toRemove.push_back(e); + } + } + + Assert(!guaranteedSuccess || !toRemove.empty()); + + if(!toRemove.empty()){ + while(!toRemove.empty()){ + ArithVar e = toRemove.back(); + toRemove.pop_back(); + reassertErrorConstraint(e, d_currentErrorVariables, true, false); + d_currentErrorVariables.remove(e); + } + + reconstructOptimizationFunction(); + } + + Debug("primal::contract") << "contractErrorVariables() end : " << d_currentErrorVariables.size() << endl; + + uint32_t exitSize = d_currentErrorVariables.size(); + + Assert(exitSize <= entrySize); + Assert(!guaranteedSuccess|| exitSize < entrySize); + return entrySize - exitSize; +} + +void SimplexDecisionProcedure::removeOptimizationFunction(){ + Assert(d_optRow != ARITHVAR_SENTINEL); + Assert(d_tableau.isBasic(d_optRow)); + + d_tableau.removeBasicRow(d_optRow); + releaseVariable(d_optRow); + + d_optRow = ARITHVAR_SENTINEL; + d_negOptConstant = d_DELTA_ZERO; + + Assert(d_optRow == ARITHVAR_SENTINEL); +} + +void SimplexDecisionProcedure::constructOptimizationFunction(){ + Assert(d_optRow == ARITHVAR_SENTINEL); + Assert(d_negOptConstant == d_DELTA_ZERO); + + d_optRow = requestVariable(); + + + std::vector coeffs; + std::vector variables; + for(ErrorMap::const_iterator iter = d_currentErrorVariables.begin(), end = d_currentErrorVariables.end(); iter != end; ++iter){ + ArithVar e = *iter; + + if(d_currentErrorVariables[e].isUpperbound()){ + coeffs.push_back(Rational(1)); + variables.push_back(e); + d_negOptConstant = d_negOptConstant + (d_currentErrorVariables[e].getConstraint()->getValue()); + }else{ + coeffs.push_back(Rational(-1)); + variables.push_back(e); + d_negOptConstant = d_negOptConstant - (d_currentErrorVariables[e].getConstraint()->getValue()); + } + } + d_tableau.addRow(d_optRow, coeffs, variables); + + DeltaRational newAssignment = d_linEq.computeRowValue(d_optRow, false); + d_partialModel.setAssignment(d_optRow, newAssignment); + + if(Debug.isOn("primal::tableau")){ d_linEq.debugCheckTableau(); } + + + if(Debug.isOn("primal::consistent")){ + d_linEq.debugEntireLinEqIsConsistent("constructOptimizationFunction"); + } + + d_pivotsSinceOptProgress = 0; + d_pivotsSinceErrorProgress = 0; + + Assert(d_optRow != ARITHVAR_SENTINEL); +} + +void SimplexDecisionProcedure::reconstructOptimizationFunction(){ + removeOptimizationFunction(); + constructOptimizationFunction(); +} + + + +/* TODO: + * Very naive implementation. Recomputes everything every time. + * Currently looks for the variable that can decrease the optimization function the most. + * + */ +SimplexDecisionProcedure::PrimalResponse SimplexDecisionProcedure::primalCheck() +{ + Debug("primal") << "primalCheck() begin" << endl; + + ArithVar leaving = ARITHVAR_SENTINEL; + ArithVar entering = ARITHVAR_SENTINEL; + DeltaRational leavingShift = d_DELTA_ZERO; // The amount the leaving variable can change by without making the tableau inconsistent + DeltaRational leavingDelta = d_DELTA_ZERO; // The amount the optimization function changes by selecting leaving + + Assert(d_improvementCandidates.empty()); + + for( Tableau::RowIterator ri = d_tableau.basicRowIterator(d_optRow); !ri.atEnd(); ++ri){ + const Tableau::Entry& e = *ri; + + ArithVar curr = e.getColVar(); + if(curr == d_optRow){ continue; } + + + int sgn = e.getCoefficient().sgn(); + Assert(sgn != 0); + if( (sgn < 0 && d_partialModel.strictlyBelowUpperBound(curr)) || + (sgn > 0 && d_partialModel.strictlyAboveLowerBound(curr)) ){ + + d_improvementCandidates.push_back(&e); + } + } + + if(d_improvementCandidates.empty()){ + Debug("primal") << "primalCheck() end : global" << endl; + return GlobalMinimum; // No variable in the optimization function can be improved + } + + DeltaRational minimumShift; + DeltaRational currShift; + for(EntryVector::const_iterator ci = d_improvementCandidates.begin(), end_ci = d_improvementCandidates.end(); ci != end_ci; ++ci){ + const Tableau::Entry& e = *(*ci); + ArithVar curr = e.getColVar(); + + ArithVar currEntering; + bool progress; + + minimumShift = (leaving == ARITHVAR_SENTINEL ) ? leavingDelta/(e.getCoefficient().abs()) : d_DELTA_ZERO; + int sgn = e.getCoefficient().sgn(); + computeShift(curr, sgn < 0, progress, currEntering, currShift, minimumShift); + + if(currEntering == ARITHVAR_SENTINEL){ + d_improvementCandidates.clear(); + + Debug("primal") << "primalCheck() end : unbounded" << endl; + d_primalCarry.d_unbounded = curr; + return FoundUnboundedVariable; + }else if(progress) { + leaving = curr; + leavingShift = currShift; + leavingDelta = currShift * e.getCoefficient(); + entering = currEntering; + + Assert(leavingDelta < d_DELTA_ZERO); + + const int RECHECK_PERIOD = 10; + if(d_pivotsSinceErrorProgress % RECHECK_PERIOD != 0){ + // we can make progress, stop + break; + } + } + } + + if(leaving == ARITHVAR_SENTINEL){ + cout << "Nothing can make progress " << endl; + + const uint32_t THRESHOLD = 20; + if(d_pivotsSinceOptProgress <= THRESHOLD){ + + int index = rand() % d_improvementCandidates.size(); + leaving = (*d_improvementCandidates[index]).getColVar(); + entering = selectFirstValid(leaving, (*d_improvementCandidates[index]).getCoefficient().sgn() < 0); + }else{ // Bland's rule + bool increasing; + for(EntryVector::const_iterator ci = d_improvementCandidates.begin(), end_ci = d_improvementCandidates.end(); ci != end_ci; ++ci){ + const Tableau::Entry& e = *(*ci); + ArithVar curr = e.getColVar(); + leaving = (leaving == ARITHVAR_SENTINEL) ? curr : minVarOrder(*this, curr, leaving); + if(leaving == curr){ + increasing = (e.getCoefficient().sgn() < 0); + } + } + + entering = selectMinimumValid(leaving, increasing); + } + Assert(leaving != ARITHVAR_SENTINEL); + Assert(entering != ARITHVAR_SENTINEL); + + d_primalCarry.d_leaving = leaving; + d_primalCarry.d_entering = entering; + + d_primalCarry.d_nextEnteringValue = d_partialModel.getAssignment(entering); + + Debug("primal") << "primalCheck() end : no progress made " << leaving << " to " << entering << " (" << d_pivotsSinceOptProgress << ")"<< endl; + d_improvementCandidates.clear(); + return NoProgressOnLeaving; + }else{ + const Tableau::Entry& enterLeavingEntry = d_tableau.findEntry(d_tableau.basicToRowIndex(entering), leaving); + Assert(!enterLeavingEntry.blank()); + + d_primalCarry.d_leaving = leaving; + d_primalCarry.d_entering = entering; + d_primalCarry.d_nextEnteringValue = d_partialModel.getAssignment(entering) + + leavingShift * enterLeavingEntry.getCoefficient(); + + Debug("primal") << "primalCheck() end : progress" << endl + << leaving << " to " << entering << " ~ " + << d_partialModel.getAssignment(leaving) << " ~ " << leavingShift + << " ~ " << enterLeavingEntry.getCoefficient() + << " ~ " << d_primalCarry.d_nextEnteringValue << endl; + + d_improvementCandidates.clear(); + return MakeProgressOnLeaving; + } + + // anyProgress = true; + + // DeltaRational currDelta = currShift * e.getCoefficient(); + + // int cmp = currDelta.cmp(leavingDelta); + + // // Cases: + // // 1) No candidate yet, + // // 2) there is a candidate with a strictly better update, or + // // 3) there is a candidate with the same update value that has a smaller value in the variable ordering. + // // + // // Case 3 covers Bland's rule. + // if(entering == ARITHVAR_SENTINEL || cmp < 0){ + // leaving = curr; + // }else if( cmp == 0 ){ + // leaving = minVarOrder(*this, curr, leaving); + // } + + // if(leaving == curr){ + // leavingShift = currShift; + // leavingDelta = currDelta; + // entering = currEntering; + // } + // } + // } + + // if(leaving == ARITHVAR_SENTINEL){ + // Debug("primal") << "primalCheck() end : global" << endl; + // return GlobalMinimum; // No variable in the optimization function can be improved + // }else{ + // const Tableau::Entry& enterLeavingEntry = d_tableau.findEntry(d_tableau.basicToRowIndex(entering), leaving); + // Assert(!enterLeavingEntry.blank()); + + // d_primalCarry.d_leaving = leaving; + // d_primalCarry.d_entering = entering; + // d_primalCarry.d_nextEnteringValue = d_partialModel.getAssignment(entering) + // + leavingShift * enterLeavingEntry.getCoefficient(); + + // Debug("primal") << "primalCheck() end : progress" << endl + // << leaving << " to " << entering << " ~ " + // << d_partialModel.getAssignment(leaving) << " ~ " << leavingShift + // << " ~ " << enterLeavingEntry.getCoefficient() + // << " ~ " << d_primalCarry.d_nextEnteringValue << endl; + // return MakeProgressOnLeaving; + // } +} + +ArithVar SimplexDecisionProcedure::selectMinimumValid(ArithVar v, bool increasing){ + ArithVar minimum = ARITHVAR_SENTINEL; + for(Tableau::ColIterator colIter = d_tableau.colIterator(v);!colIter.atEnd(); ++colIter){ + const Tableau::Entry& e = *colIter; + ArithVar basic = d_tableau.rowIndexToBasic(e.getRowIndex()); + if(basic == d_optRow) continue; + + + int esgn = e.getCoefficient().sgn(); + bool basicInc = (increasing == (esgn > 0)); + + if(!(basicInc ? d_partialModel.strictlyBelowUpperBound(basic) : + d_partialModel.strictlyAboveLowerBound(basic))){ + if(minimum == ARITHVAR_SENTINEL){ + minimum = basic; + }else{ + minimum = minVarOrder(*this, basic, minimum); + } + } + } + return minimum; +} + +ArithVar SimplexDecisionProcedure::selectFirstValid(ArithVar v, bool increasing){ + ArithVar minimum = ARITHVAR_SENTINEL; + + for(Tableau::ColIterator colIter = d_tableau.colIterator(v);!colIter.atEnd(); ++colIter){ + const Tableau::Entry& e = *colIter; + ArithVar basic = d_tableau.rowIndexToBasic(e.getRowIndex()); + if(basic == d_optRow) continue; + + int esgn = e.getCoefficient().sgn(); + bool basicInc = (increasing == (esgn > 0)); + + if(!(basicInc ? d_partialModel.strictlyBelowUpperBound(basic) : + d_partialModel.strictlyAboveLowerBound(basic))){ + if(minimum == ARITHVAR_SENTINEL){ + minimum = basic; + }else{ + minimum = minRowLength(*this, basic, minimum); + } + } + } + return minimum; +} + + + +void SimplexDecisionProcedure::computeShift(ArithVar leaving, bool increasing, bool& progress, ArithVar& entering, DeltaRational& shift, const DeltaRational& minimumShift){ + Assert(increasing ? (minimumShift >= d_DELTA_ZERO) : (minimumShift <= d_DELTA_ZERO) ); + + static int instance = 0; + Debug("primal") << "computeshift " << ++instance << " " << leaving << endl; + + // The selection for the leaving variable + entering = ARITHVAR_SENTINEL; + + // no progress is initially made + progress = false; + + bool bounded = false; + + if(increasing ? d_partialModel.hasUpperBound(leaving) : d_partialModel.hasLowerBound(leaving)){ + const DeltaRational& assignment = d_partialModel.getAssignment(leaving); + + bounded = true; + + DeltaRational diff = increasing ? d_partialModel.getUpperBound(leaving) - assignment : d_partialModel.getLowerBound(leaving) - assignment; + Assert(increasing ? diff.sgn() >=0 : diff.sgn() <= 0); + if((increasing) ? (diff < minimumShift) : ( diff > minimumShift)){ + Assert(!progress); + entering = leaving; // My my my, what an ugly hack + return; // no progress is possible stop + } + } + + // shift has a meaningful value once entering has a meaningful value + // if increasing, + // then shift > minimumShift >= 0 + // else shift < minimumShift <= 0 + // + // Maintain the following invariant: + // + // if increasing, + // if e_ij > 0, diff >= shift > minimumShift >= 0 + // if e_ij < 0, diff >= shift > minimumShift >= 0 + // if !increasing, + // if e_ij > 0, diff <= shift < minimumShift <= 0 + // if e_ij < 0, diff <= shift < minimumShift <= 0 + // if increasing == (e_ij > 0), diff = (d_partialModel.getUpperBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient() + // if increasing != (e_ij > 0), diff = (d_partialModel.getLowerBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient() + + for(Tableau::ColIterator colIter = d_tableau.colIterator(leaving);!colIter.atEnd(); ++colIter){ + const Tableau::Entry& e = *colIter; + + ArithVar basic = d_tableau.rowIndexToBasic(e.getRowIndex()); + if(basic == d_optRow) continue; + + int esgn = e.getCoefficient().sgn(); + bool basicInc = (increasing == (esgn > 0)); + // If both are true, increasing the variable entering increases the basic variable + // If both are false, the entering variable is decreasing, but the coefficient is negative and the basic variable is increasing + // If exactly one is false, the basic variable is decreasing. + + Debug("primal::shift") << basic << " " << d_partialModel.hasUpperBound(basic) << " " + << d_partialModel.hasLowerBound(basic) << " " + << e.getCoefficient() << endl; + + if( (basicInc && d_partialModel.hasUpperBound(basic))|| + (!basicInc && d_partialModel.hasLowerBound(basic))){ + + if(!(basicInc ? d_partialModel.strictlyBelowUpperBound(basic) : + d_partialModel.strictlyAboveLowerBound(basic))){ + // diff == 0, as diff > minimumShift >= 0 or diff < minimumShift <= 0 + Assert(!progress); + entering = basic; + return; + }else{ + DeltaRational diff = basicInc ? + (d_partialModel.getUpperBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient() : + (d_partialModel.getLowerBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient(); + + if( entering == ARITHVAR_SENTINEL ){ + if(increasing ? (diff <= minimumShift) : (diff >= minimumShift)){ + Assert(!progress); + entering = basic; + return; + }else{ + Assert(increasing ? (diff > minimumShift) : (diff < minimumShift)); + shift = diff; + entering = basic; + bounded = true; + } + }else{ + if( increasing ? (diff < shift) : diff > shift){ + // a new min for increasing + // a new max for decreasing + + if(increasing ? (diff <= minimumShift) : (diff >= minimumShift)){ + Assert(!progress); + entering = basic; + return; + }else{ + Assert(increasing ? (diff > minimumShift) : (diff < minimumShift)); + shift = diff; + entering = basic; + } + } + } + } + } + } + + if(!bounded){ + // A totally unbounded variable + Assert(entering == ARITHVAR_SENTINEL); + progress = true; + return; + }else if(entering == ARITHVAR_SENTINEL){ + // We have a variable that is bounded only by its maximum + for(Tableau::ColIterator colIter = d_tableau.colIterator(leaving);!colIter.atEnd(); ++colIter){ + const Tableau::Entry& e = *colIter; + + ArithVar basic = d_tableau.rowIndexToBasic(e.getRowIndex()); + if(basic == d_optRow){ + continue; + } else{ + entering = basic; + break; + } + } + Assert(entering != ARITHVAR_SENTINEL); + + Assert(increasing ? d_partialModel.hasUpperBound(leaving) : d_partialModel.hasLowerBound(leaving)); + + const DeltaRational& assignment = d_partialModel.getAssignment(leaving); + DeltaRational diff = increasing ? d_partialModel.getUpperBound(leaving) - assignment : d_partialModel.getLowerBound(leaving) - assignment; + + shift = diff; + + Assert(increasing ? shift.sgn() >=0 : shift.sgn() <= 0); + Assert(increasing ? shift > minimumShift : shift < minimumShift); + + progress = true; + return; + }else{ + Assert(bounded); + progress = true; + + if((increasing ? d_partialModel.hasUpperBound(leaving) : d_partialModel.hasLowerBound(leaving) )){ + Assert(entering != ARITHVAR_SENTINEL); + const DeltaRational& assignment = d_partialModel.getAssignment(leaving); + DeltaRational diff = increasing ? d_partialModel.getUpperBound(leaving) - assignment : d_partialModel.getLowerBound(leaving) - assignment; + if((increasing) ? (diff < shift) : ( diff > shift)){ + shift = diff; + } + } + + Assert(increasing ? shift.sgn() >=0 : shift.sgn() <= 0); + Assert(increasing ? shift > minimumShift : shift < minimumShift); + return; + } + + + // if(! bounded || + // (increasing && diff < shift) || // a new min for increasing + // (!increasing && diff > shift)){ // a new max for decreasing + // bounded = true; + // shift = diff; + // entering = basic; + // } + // } + + // if(notAtTheBound && !blandMode){ + // DeltaRational diff = basicInc ? + // (d_partialModel.getUpperBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient() : + // (d_partialModel.getLowerBound(basic) - d_partialModel.getAssignment(basic)) / e.getCoefficient(); + + // if(! bounded || + // (increasing && diff < shift) || // a new min for increasing + // (!increasing && diff > shift)){ // a new max for decreasing + // bounded = true; + // shift = diff; + // entering = basic; + // } + // }else if (!notAtTheBound) { // basic is already exactly at the bound + // if(!blandMode){ // Enter into using Bland's rule + // blandMode = true; + // bounded = true; + // shift = d_DELTA_ZERO; + // entering = basic; + // }else{ + // entering = minVarOrder(*this, entering, basic); // Bland's rule. + // } + // } + // else this basic variable cannot be violated by increasing/decreasing entering + + + + + // if(!blandMode && (increasing ? d_partialModel.hasUpperBound(leaving) : d_partialModel.hasLowerBound(leaving) )){ + // Assert(entering != ARITHVAR_SENTINEL); + // bounded = true; + // DeltaRational diff = increasing ? d_partialModel.getUpperBound(leaving) - assignment : d_partialModel.getLowerBound(leaving) - assignment; + // if((increasing) ? (diff < shift) : ( diff > shift)){ + // shift = diff; + // } + // } + + // Assert(increasing ? shift.sgn() >=0 : shift.sgn() <= 0); + + // return shift; +} + + +/** + * Given an variable on the optimization row that can be used to decrease the value of the optimization function + * arbitrarily and an optimization function that is strictly positive in the current model, + * driveOptToZero updates the value of unbounded s.t. the value of d_opt is exactly 0. + */ +void SimplexDecisionProcedure::driveOptToZero(ArithVar unbounded){ + Assert(!belowThreshold()); + + const Tableau::Entry& e = d_tableau.findEntry(d_tableau.basicToRowIndex(d_optRow), unbounded); + Assert(!e.blank()); + + DeltaRational theta = (d_negOptConstant-d_partialModel.getAssignment(d_optRow))/ (e.getCoefficient()); + Assert((e.getCoefficient().sgn() > 0) ? (theta.sgn() < 0) : (theta.sgn() > 0)); + + DeltaRational newAssignment = d_partialModel.getAssignment(unbounded) + theta; + d_linEq.update(unbounded, newAssignment); + + if(Debug.isOn("primal::consistent")){ Assert(d_linEq.debugEntireLinEqIsConsistent("driveOptToZero")); } + + Assert(belowThreshold()); +} diff --git a/src/theory/arith/simplex-converge.h b/src/theory/arith/simplex-converge.h new file mode 100644 index 000000000..dac3a9b49 --- /dev/null +++ b/src/theory/arith/simplex-converge.h @@ -0,0 +1,531 @@ +/********************* */ +/*! \file simplex.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): none + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009, 2010, 2011 The Analysis of Computer Systems Group (ACSys) + ** Courant Institute of Mathematical Sciences + ** New York University + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#ifndef __CVC4__THEORY__ARITH__SIMPLEX_H +#define __CVC4__THEORY__ARITH__SIMPLEX_H + +#include "theory/arith/arithvar.h" +#include "theory/arith/arith_priority_queue.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/matrix.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/linear_equality.h" + +#include "context/cdlist.h" + +#include "util/dense_map.h" +#include "options/options.h" +#include "util/stats.h" +#include "util/result.h" + +#include + +namespace CVC4 { +namespace theory { +namespace arith { + +class SimplexDecisionProcedure { +private: + ArithVar d_conflictVariable; + DenseSet d_successes; + + /** Linear equality module. */ + LinearEqualityModule& d_linEq; + + /** + * Manages information about the assignment and upper and lower bounds on + * variables. + * Partial model matches that in LinearEqualityModule. + */ + ArithPartialModel& d_partialModel; + + /** + * 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. */ + ArithPriorityQueue d_queue; + + /** 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. */ + NodeCallBack& d_conflictChannel; + + /** Maps a variable to how many times they have been used as a pivot in the simplex search. */ + DenseMultiset d_pivotsInRound; + + /** Stores to the DeltaRational constant 0. */ + DeltaRational d_DELTA_ZERO; + + //TODO make an option + const static uint32_t MAX_ITERATIONS = 20; + + + /** Used for requesting d_opt, bound and error variables for primal.*/ + ArithVarMalloc& d_arithVarMalloc; + + /** Used for constructing temporary variables, bound and error variables for primal.*/ + ConstraintDatabase& d_constraintDatabase; + +public: + SimplexDecisionProcedure(LinearEqualityModule& linEq, + NodeCallBack& conflictChannel, + ArithVarMalloc& variables, + ConstraintDatabase& constraintDatabase); + + /** + * This must be called when the value of a basic variable may now voilate one + * of its bounds. + */ + void updateBasic(ArithVar x){ + d_queue.enqueueIfInconsistent(x); + } + + /** + * 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]. + */ + Result::Sat dualFindModel(bool exactResult); + + + /** + * Tries to update the assignments of the variables s.t. all of the assignments + * are consistent with their bounds. + * + * This is a REALLY heavy hammer consider calling dualFindModel(false) first. + * + * !!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!! + * This procedure needs to temporarily relax contraints to contruct a satisfiable system. + * To do this, it is going to do a sat push. + */ + Result::Sat primalFindModel(); + +private: + + + /** + * 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. + * + * Both ArithVar must be non-basic in d_tableau. + */ + typedef ArithVar (*PreferenceFunction)(const SimplexDecisionProcedure&, ArithVar, ArithVar); + + /** + * minVarOrder is a PreferenceFunction for selecting the smaller of the 2 ArithVars. + * This PreferenceFunction is used during the VarOrder stage of + * findModel. + */ + static ArithVar minVarOrder(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + + /** + * minRowCount 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. + */ + static ArithVar minColLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + static ArithVar minRowLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + + /** + * 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. + */ + static ArithVar minBoundAndRowCount(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + + + /** + * 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); + + enum SearchPeriod {BeforeDiffSearch, DuringDiffSearch, AfterDiffSearch, DuringVarOrderSearch, AfterVarOrderSearch}; + + bool findConflictOnTheQueue(SearchPeriod period); + + + /** + * 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, PreferenceFunction pf); + ArithVar selectSlackLowerBound(ArithVar x_i, PreferenceFunction pf = minVarOrder) { + return selectSlack(x_i, pf); + } + ArithVar selectSlackUpperBound(ArithVar x_i, PreferenceFunction pf = minVarOrder) { + return selectSlack(x_i, pf); + } + /** + * Returns the smallest basic variable whose assignment is not consistent + * with its upper and lower bounds. + */ + ArithVar selectSmallestInconsistentVar(); + + /** + * Given a non-basic variable that is know to not be updatable + * to a consistent value, construct and return a conflict. + * Follows section 4.2 in the CAV06 paper. + */ + Node generateConflictAboveUpperBound(ArithVar conflictVar); + Node generateConflictBelowLowerBound(ArithVar conflictVar); + +public: + void increaseMax() {d_numVariables++;} + + + void clearQueue() { + d_queue.clear(); + } + + + bool debugIsInCollectionQueue(ArithVar var) const{ + Assert(d_queue.inCollectionMode()); + return d_queue.collectionModeContains(var); + } + + void reduceQueue(){ + d_queue.reduce(); + } + + ArithPriorityQueue::const_iterator queueBegin() const{ + return d_queue.begin(); + } + + ArithPriorityQueue::const_iterator queueEnd() const{ + return d_queue.end(); + } + +private: + + /** Reports a conflict to on the output channel. */ + void reportConflict(Node conflict){ + d_conflictChannel(conflict); + ++(d_statistics.d_simplexConflicts); + } + + template + inline bool isAcceptableSlack(int sgn, ArithVar nonbasic){ + return + ( above && sgn < 0 && d_partialModel.strictlyBelowUpperBound(nonbasic)) || + ( above && sgn > 0 && d_partialModel.strictlyAboveLowerBound(nonbasic)) || + (!above && sgn > 0 && d_partialModel.strictlyBelowUpperBound(nonbasic)) || + (!above && sgn < 0 && d_partialModel.strictlyAboveLowerBound(nonbasic)); + } + + /** + * 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. + */ + Node checkBasicForConflict(ArithVar b); + + Node weakenConflict(bool aboveUpper, ArithVar basicVar); + Constraint weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic); + + /** 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); + } + + + /** An error info keeps the information associated with the construction of an ErrorVariable. */ + class ErrorInfo { + private: + /** The variable for which the error variable was constructed.*/ + ArithVar d_variable; + + // If false -> lowerbound + bool d_upperbound; + + /** The violated constraint this was constructed to try to satisfy.*/ + Constraint d_violated; + + public: + ErrorInfo(ArithVar error, bool ub, const Constraint original) + : d_variable(error), d_upperbound(ub), d_violated(original) {} + + ErrorInfo() : + d_variable(ARITHVAR_SENTINEL), d_upperbound(false), d_violated(NullConstraint){} + + inline ArithVar getVariable() const { + return d_variable; + } + + inline bool isUpperbound() const { + return d_upperbound; + } + + inline bool errorIsLeqZero(const ArithPartialModel& d_pm) const{ + return isUpperbound() ? + (d_pm.getAssignment(d_variable) <= d_violated->getValue()) : + (d_pm.getAssignment(d_variable) >= d_violated->getValue()); + } + + inline Constraint getConstraint() const { + return d_violated; + } + + inline DeltaRational getErrorAmount(const ArithPartialModel& d_pm) const { + return isUpperbound() ? + (d_pm.getAssignment(d_variable) - d_violated->getValue()) : + (d_violated->getValue() - d_pm.getAssignment(d_variable)); + } + }; + + typedef DenseMap ErrorMap; + + /** A map from the error variables to the associated ErrorInfo.*/ + ErrorMap d_currentErrorVariables; + + /** The optimization function is implicitly defined as + * f_i = d_optRow - d_negOptConstant + * + * d_optRow is a basic variable in the tableau. + * The tableau maintains that it is equal to the sum of -1^{!ub} * the error variables in + * d_currentErrorVariables. + * + * d_negOptConstant is explicitly the negation of the sum of the bounds that were violated + * + * assignment(f_i) <= 0 iff assignment(d_optRow) <= d_negOptConstant + */ + /** The variable for the variable part of the optimization function.*/ + ArithVar d_optRow; + + /** The constant part of the optimization function.*/ + DeltaRational d_negOptConstant; + + inline bool belowThreshold() const { + return d_partialModel.getAssignment(d_optRow) <= d_negOptConstant; + } + + /** + * A PrimalResponse represents the state that the primal simplex solver is in. + */ + enum PrimalResponse { + // The optimization can decrease arbitrarily on some variable in the function + FoundUnboundedVariable, + + // The optimization function has reached a threshold value and is checking back in + ReachedThresholdValue, + + // Simplex has used up its pivot bound and is checking back in with its caller + UsedMaxPivots, + + //Simplex can make progress on the pair of entering and leaving variables + MakeProgressOnLeaving, + + //Simplex is not at a minimum but no leaving variable can be changed to help + NoProgressOnLeaving, + + // Simplex has reached a minimum for its optimization function + GlobalMinimum + }; + + /** + * These fields are for sticking information for passing information back with the PrimalRespones. + * These fields should be ignored as unsafe/unknown unless you have a PrimalResponse that states + * the field makes sense. + */ + struct PrimalPassBack { + public: + /** + * A variable s.t. its value can be increased/decreased arbitrarily to change the optimization function + * arbitrarily low. + */ + ArithVar d_unbounded; + + /** The leaving variable selection for primal simplex. */ + ArithVar d_leaving; + + /** The entering variable selection for primal simplex. */ + ArithVar d_entering; + + /** The new value for the leaving variable value for primal simplex.*/ + DeltaRational d_nextEnteringValue; + + PrimalPassBack() { clear(); } + void clear(){ + d_unbounded = (d_leaving = (d_entering = ARITHVAR_SENTINEL)); + d_nextEnteringValue = DeltaRational(); + } + + bool isClear() const { + return d_unbounded == ARITHVAR_SENTINEL && + d_leaving == ARITHVAR_SENTINEL && + d_entering == ARITHVAR_SENTINEL && + d_nextEnteringValue.sgn() == 0; + } + } d_primalCarry; + + uint32_t d_pivotsSinceErrorProgress; + uint32_t d_pivotsSinceOptProgress; + uint32_t d_pivotsSinceLastCheck; + + typedef std::vector< const Tableau::Entry* > EntryVector; + EntryVector d_improvementCandidates; + + PrimalResponse primal(uint32_t maxIterations); + PrimalResponse primalCheck(); + Result::Sat primalConverge(int depth); + void driveOptToZero(ArithVar unbounded); + uint32_t contractErrorVariables(bool guaranteedSuccess); + bool checkForRowConflicts(); + void restoreErrorVariables(ErrorMap& es); + void constructErrorVariables(); + void constructOptimizationFunction(); + void removeOptimizationFunction(); + void reconstructOptimizationFunction(); + ArithVar selectMinimumValid(ArithVar v, bool increasing); + ArithVar selectFirstValid(ArithVar v, bool increasing); + + void reassertErrorConstraint(ArithVar e, ErrorMap& es, bool definitelyTrue, bool definitelyFalse); + void computeShift(ArithVar leaving, bool increasing, bool& progress, ArithVar& entering, DeltaRational& shift, const DeltaRational& minimumShift); + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_statUpdateConflicts; + + TimerStat d_findConflictOnTheQueueTime; + + IntStat d_attemptBeforeDiffSearch, d_successBeforeDiffSearch; + IntStat d_attemptAfterDiffSearch, d_successAfterDiffSearch; + IntStat d_attemptDuringDiffSearch, d_successDuringDiffSearch; + IntStat d_attemptDuringVarOrderSearch, d_successDuringVarOrderSearch; + IntStat d_attemptAfterVarOrderSearch, d_successAfterVarOrderSearch; + + IntStat d_weakeningAttempts, d_weakeningSuccesses, d_weakenings; + TimerStat d_weakenTime; + + + IntStat d_simplexConflicts; + + // Primal stuffs + TimerStat d_primalTimer; + TimerStat d_internalTimer; + + IntStat d_primalCalls; + IntStat d_primalSatCalls; + IntStat d_primalUnsatCalls; + + IntStat d_primalPivots; + IntStat d_primalImprovingPivots; + + IntStat d_primalThresholdReachedPivot; + IntStat d_primalThresholdReachedPivot_dropped; + + IntStat d_primalReachedMaxPivots; + IntStat d_primalReachedMaxPivots_contractMadeProgress; + IntStat d_primalReachedMaxPivots_checkForConflictWorked; + + + IntStat d_primalGlobalMinimum; + IntStat d_primalGlobalMinimum_rowConflictWorked; + IntStat d_primalGlobalMinimum_firstHalfWasSat; + IntStat d_primalGlobalMinimum_firstHalfWasUnsat; + IntStat d_primalGlobalMinimum_contractMadeProgress; + + + IntStat d_unboundedFound; + IntStat d_unboundedFound_drive; + IntStat d_unboundedFound_dropped; + + + Statistics(); + ~Statistics(); + }; + + Statistics d_statistics; + +};/* class SimplexDecisionProcedure */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + +#endif /* __CVC4__THEORY__ARITH__SIMPLEX_H */ + diff --git a/src/theory/arith/simplex.cpp b/src/theory/arith/simplex.cpp index ea8fefc6f..02e49258c 100644 --- a/src/theory/arith/simplex.cpp +++ b/src/theory/arith/simplex.cpp @@ -18,575 +18,234 @@ #include "theory/arith/simplex.h" #include "theory/arith/options.h" +#include "theory/arith/constraint.h" using namespace std; -using namespace CVC4; -using namespace CVC4::kind; - -using namespace CVC4::theory; -using namespace CVC4::theory::arith; - -static const bool CHECK_AFTER_PIVOT = true; - -SimplexDecisionProcedure::SimplexDecisionProcedure(LinearEqualityModule& linEq, NodeCallBack& conflictChannel) : - d_conflictVariable(ARITHVAR_SENTINEL), - d_linEq(linEq), - d_partialModel(d_linEq.getPartialModel()), - d_tableau(d_linEq.getTableau()), - d_queue(d_partialModel, d_tableau), - d_numVariables(0), - d_conflictChannel(conflictChannel), - d_pivotsInRound(), - d_DELTA_ZERO(0,0) +namespace CVC4 { +namespace theory { +namespace arith { + +SimplexDecisionProcedure::SimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc) + : 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_arithVarMalloc(tvmalloc) + , d_errorSize(0) + , d_zero(0) { - switch(ArithHeuristicPivotRule rule = options::arithHeuristicPivotRule()) { - case MINIMUM: - d_queue.setPivotRule(ArithPriorityQueue::MINIMUM); - break; - case BREAK_TIES: - d_queue.setPivotRule(ArithPriorityQueue::BREAK_TIES); - break; - case MAXIMUM: - d_queue.setPivotRule(ArithPriorityQueue::MAXIMUM); - break; - default: - Unhandled(rule); - } + d_heuristicRule = options::arithErrorSelectionRule(); + d_errorSet.setSelectionRule(d_heuristicRule); } -SimplexDecisionProcedure::Statistics::Statistics(): - d_statUpdateConflicts("theory::arith::UpdateConflicts", 0), - d_findConflictOnTheQueueTime("theory::arith::findConflictOnTheQueueTime"), - d_attemptBeforeDiffSearch("theory::arith::qi::BeforeDiffSearch::attempt",0), - d_successBeforeDiffSearch("theory::arith::qi::BeforeDiffSearch::success",0), - d_attemptAfterDiffSearch("theory::arith::qi::AfterDiffSearch::attempt",0), - d_successAfterDiffSearch("theory::arith::qi::AfterDiffSearch::success",0), - d_attemptDuringDiffSearch("theory::arith::qi::DuringDiffSearch::attempt",0), - d_successDuringDiffSearch("theory::arith::qi::DuringDiffSearch::success",0), - d_attemptDuringVarOrderSearch("theory::arith::qi::DuringVarOrderSearch::attempt",0), - d_successDuringVarOrderSearch("theory::arith::qi::DuringVarOrderSearch::success",0), - d_attemptAfterVarOrderSearch("theory::arith::qi::AfterVarOrderSearch::attempt",0), - d_successAfterVarOrderSearch("theory::arith::qi::AfterVarOrderSearch::success",0), - d_weakeningAttempts("theory::arith::weakening::attempts",0), - d_weakeningSuccesses("theory::arith::weakening::success",0), - d_weakenings("theory::arith::weakening::total",0), - d_weakenTime("theory::arith::weakening::time"), - d_simplexConflicts("theory::arith::simplexConflicts",0) -{ - StatisticsRegistry::registerStat(&d_statUpdateConflicts); - - StatisticsRegistry::registerStat(&d_findConflictOnTheQueueTime); - - StatisticsRegistry::registerStat(&d_attemptBeforeDiffSearch); - StatisticsRegistry::registerStat(&d_successBeforeDiffSearch); - StatisticsRegistry::registerStat(&d_attemptAfterDiffSearch); - StatisticsRegistry::registerStat(&d_successAfterDiffSearch); - StatisticsRegistry::registerStat(&d_attemptDuringDiffSearch); - StatisticsRegistry::registerStat(&d_successDuringDiffSearch); - StatisticsRegistry::registerStat(&d_attemptDuringVarOrderSearch); - StatisticsRegistry::registerStat(&d_successDuringVarOrderSearch); - StatisticsRegistry::registerStat(&d_attemptAfterVarOrderSearch); - StatisticsRegistry::registerStat(&d_successAfterVarOrderSearch); - - StatisticsRegistry::registerStat(&d_weakeningAttempts); - StatisticsRegistry::registerStat(&d_weakeningSuccesses); - StatisticsRegistry::registerStat(&d_weakenings); - StatisticsRegistry::registerStat(&d_weakenTime); - - StatisticsRegistry::registerStat(&d_simplexConflicts); -} +bool SimplexDecisionProcedure::standardProcessSignals(TimerStat &timer, IntStat& conflicts) { + TimerStat::CodeTimer codeTimer(timer); + Assert( d_conflictVariables.empty() ); -SimplexDecisionProcedure::Statistics::~Statistics(){ - StatisticsRegistry::unregisterStat(&d_statUpdateConflicts); - StatisticsRegistry::unregisterStat(&d_findConflictOnTheQueueTime); + while(d_errorSet.moreSignals()){ + ArithVar curr = d_errorSet.topSignal(); + if(d_tableau.isBasic(curr) && !d_variables.assignmentIsConsistent(curr)){ + Assert(d_linEq.basicIsTracked(curr)); - StatisticsRegistry::unregisterStat(&d_attemptBeforeDiffSearch); - StatisticsRegistry::unregisterStat(&d_successBeforeDiffSearch); - StatisticsRegistry::unregisterStat(&d_attemptAfterDiffSearch); - StatisticsRegistry::unregisterStat(&d_successAfterDiffSearch); - StatisticsRegistry::unregisterStat(&d_attemptDuringDiffSearch); - StatisticsRegistry::unregisterStat(&d_successDuringDiffSearch); - StatisticsRegistry::unregisterStat(&d_attemptDuringVarOrderSearch); - StatisticsRegistry::unregisterStat(&d_successDuringVarOrderSearch); - StatisticsRegistry::unregisterStat(&d_attemptAfterVarOrderSearch); - StatisticsRegistry::unregisterStat(&d_successAfterVarOrderSearch); + if(!d_conflictVariables.isMember(curr) && checkBasicForConflict(curr)){ - StatisticsRegistry::unregisterStat(&d_weakeningAttempts); - StatisticsRegistry::unregisterStat(&d_weakeningSuccesses); - StatisticsRegistry::unregisterStat(&d_weakenings); - StatisticsRegistry::unregisterStat(&d_weakenTime); + Debug("recentlyViolated") + << "It worked? " + << conflicts.getData() + << " " << 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(); - StatisticsRegistry::unregisterStat(&d_simplexConflicts); + 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)); + Node conflict = generateConflictForBasic(basic); - - - - - -ArithVar SimplexDecisionProcedure::minVarOrder(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(!simp.d_tableau.isBasic(x)); - Assert(!simp.d_tableau.isBasic(y)); - if(x <= y){ - return x; - } else { - return y; - } + static bool verbose = false; + if(verbose) { Message() << "conflict " << basic << " " << conflict << endl; } + Assert(!conflict.isNull()); + d_conflictChannel(conflict); + d_conflictVariables.add(basic); } -ArithVar SimplexDecisionProcedure::minColLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(!simp.d_tableau.isBasic(x)); - Assert(!simp.d_tableau.isBasic(y)); - uint32_t xLen = simp.d_tableau.getColLength(x); - uint32_t yLen = simp.d_tableau.getColLength(y); - if( xLen > yLen){ - return y; - } else if( xLen== yLen ){ - return minVarOrder(simp,x,y); - }else{ - return x; - } -} +Node SimplexDecisionProcedure::generateConflictForBasic(ArithVar basic) const { -ArithVar SimplexDecisionProcedure::minBoundAndRowCount(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y){ - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(!simp.d_tableau.isBasic(x)); - Assert(!simp.d_tableau.isBasic(y)); - if(simp.d_partialModel.hasEitherBound(x) && !simp.d_partialModel.hasEitherBound(y)){ - return y; - }else if(!simp.d_partialModel.hasEitherBound(x) && simp.d_partialModel.hasEitherBound(y)){ - return x; - }else { - return minColLength(simp, x, y); + Assert(d_tableau.isBasic(basic)); + Assert(checkBasicForConflict(basic)); + + if(d_variables.cmpAssignmentLowerBound(basic) < 0){ + Assert(d_linEq.nonbasicsAtUpperBounds(basic)); + return d_linEq.generateConflictBelowLowerBound(basic); + }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ + Assert(d_linEq.nonbasicsAtLowerBounds(basic)); + return d_linEq.generateConflictAboveUpperBound(basic); + }else{ + Unreachable(); } } - -template -ArithVar SimplexDecisionProcedure::selectSlack(ArithVar x_i, SimplexDecisionProcedure::PreferenceFunction pref){ - 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 : pref(*this, slack, nonbasic); - } +Node SimplexDecisionProcedure::maybeGenerateConflictForBasic(ArithVar basic) const { + if(checkBasicForConflict(basic)){ + return generateConflictForBasic(basic); + }else{ + return Node::null(); } - - return slack; } -Node betterConflict(TNode x, TNode y){ - if(x.isNull()) return y; - else if(y.isNull()) return x; - else if(x.getNumChildren() <= y.getNumChildren()) return x; - else return y; -} - -bool SimplexDecisionProcedure::findConflictOnTheQueue(SearchPeriod type) { - TimerStat::CodeTimer codeTimer(d_statistics.d_findConflictOnTheQueueTime); - Assert(d_successes.empty()); - - switch(type){ - case BeforeDiffSearch: ++(d_statistics.d_attemptBeforeDiffSearch); break; - case DuringDiffSearch: ++(d_statistics.d_attemptDuringDiffSearch); break; - case AfterDiffSearch: ++(d_statistics.d_attemptAfterDiffSearch); break; - case DuringVarOrderSearch: ++(d_statistics.d_attemptDuringVarOrderSearch); break; - case AfterVarOrderSearch: ++(d_statistics.d_attemptAfterVarOrderSearch); break; - } - - ArithPriorityQueue::const_iterator i = d_queue.begin(); - ArithPriorityQueue::const_iterator end = d_queue.end(); - for(; i != end; ++i){ - ArithVar x_i = *i; +bool SimplexDecisionProcedure::checkBasicForConflict(ArithVar basic) const { + Assert(d_tableau.isBasic(basic)); + Assert(d_linEq.basicIsTracked(basic)); - if(x_i != d_conflictVariable && d_tableau.isBasic(x_i) && !d_successes.isMember(x_i)){ - Node possibleConflict = checkBasicForConflict(x_i); - if(!possibleConflict.isNull()){ - d_successes.add(x_i); - reportConflict(possibleConflict); - } + if(d_variables.cmpAssignmentLowerBound(basic) < 0){ + if(d_linEq.nonbasicsAtUpperBounds(basic)){ + return true; } - } - if(!d_successes.empty()){ - switch(type){ - case BeforeDiffSearch: ++(d_statistics.d_successBeforeDiffSearch); break; - case DuringDiffSearch: ++(d_statistics.d_successDuringDiffSearch); break; - case AfterDiffSearch: ++(d_statistics.d_successAfterDiffSearch); break; - case DuringVarOrderSearch: ++(d_statistics.d_successDuringVarOrderSearch); break; - case AfterVarOrderSearch: ++(d_statistics.d_successAfterVarOrderSearch); break; + }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ + if(d_linEq.nonbasicsAtLowerBounds(basic)){ + return true; } - d_successes.purge(); - return true; - }else{ - return false; } + return false; } -Result::Sat SimplexDecisionProcedure::findModel(bool exactResult){ - Assert(d_conflictVariable == ARITHVAR_SENTINEL); - Assert(d_queue.inCollectionMode()); +void SimplexDecisionProcedure::tearDownInfeasiblityFunction(TimerStat& timer, ArithVar tmp){ + TimerStat::CodeTimer codeTimer(timer); + Assert(tmp != ARITHVAR_SENTINEL); + Assert(d_tableau.isBasic(tmp)); - if(d_queue.empty()){ - return Result::SAT; - } - static CVC4_THREADLOCAL(unsigned int) instance = 0; - instance = instance + 1; - Debug("arith::findModel") << "begin findModel()" << instance << endl; + d_tableau.removeBasicRow(tmp); + releaseVariable(tmp); +} - d_queue.transitionToDifferenceMode(); +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; - Result::Sat result = Result::SAT_UNKNOWN; + int focusSgn = d_errorSet.focusSgn(back); + Rational chg(-focusSgn); - if(d_queue.empty()){ - result = Result::SAT; - }else if(d_queue.size() > 1){ - if(findConflictOnTheQueue(BeforeDiffSearch)){ - result = Result::UNSAT; - } + d_linEq.substitutePlusTimesConstant(inf, back, chg); } - static const bool verbose = false; - exactResult |= options::arithStandardCheckVarOrderPivots() < 0; - const uint32_t inexactResultsVarOrderPivots = exactResult ? 0 : options::arithStandardCheckVarOrderPivots(); - - uint32_t checkPeriod = options::arithSimplexCheckPeriod(); - if(result == Result::SAT_UNKNOWN){ - uint32_t numDifferencePivots = options::arithHeuristicPivots() < 0 ? - d_numVariables + 1 : options::arithHeuristicPivots(); - // The signed to unsigned conversion is safe. - uint32_t pivotsRemaining = numDifferencePivots; - while(!d_queue.empty() && - result != Result::UNSAT && - pivotsRemaining > 0){ - uint32_t pivotsToDo = min(checkPeriod, pivotsRemaining); - pivotsRemaining -= pivotsToDo; - if(searchForFeasibleSolution(pivotsToDo)){ - result = Result::UNSAT; - }//Once every CHECK_PERIOD examine the entire queue for conflicts - if(result != Result::UNSAT){ - if(findConflictOnTheQueue(DuringDiffSearch)) { result = Result::UNSAT; } - }else{ - findConflictOnTheQueue(AfterDiffSearch); // already unsat - } - } +} - if(verbose && numDifferencePivots > 0){ - if(result == Result::UNSAT){ - Message() << "diff order found unsat" << endl; - }else if(d_queue.empty()){ - Message() << "diff order found model" << endl; - }else{ - Message() << "diff order missed" << endl; - } - } - } +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; - if(!d_queue.empty() && result != Result::UNSAT){ - if(exactResult){ - d_queue.transitionToVariableOrderMode(); - - while(!d_queue.empty() && result != Result::UNSAT){ - if(searchForFeasibleSolution(checkPeriod)){ - result = Result::UNSAT; - } - - //Once every CHECK_PERIOD examine the entire queue for conflicts - if(result != Result::UNSAT){ - if(findConflictOnTheQueue(DuringVarOrderSearch)){ - result = Result::UNSAT; - } - } else{ - findConflictOnTheQueue(AfterVarOrderSearch); - } - } - if(verbose){ - if(result == Result::UNSAT){ - Message() << "bland found unsat" << endl; - }else if(d_queue.empty()){ - Message() << "bland found model" << endl; - }else{ - Message() << "bland order missed" << endl; - } - } + Rational chg(focusChange); + if(d_tableau.isBasic(v)){ + d_linEq.substitutePlusTimesConstant(inf, v, chg); }else{ - d_queue.transitionToVariableOrderMode(); - - if(searchForFeasibleSolution(inexactResultsVarOrderPivots)){ - result = Result::UNSAT; - findConflictOnTheQueue(AfterVarOrderSearch); // already unsat - }else{ - if(findConflictOnTheQueue(AfterVarOrderSearch)) { result = Result::UNSAT; } - } - - if(verbose){ - if(result == Result::UNSAT){ - Message() << "restricted var order found unsat" << endl; - }else if(d_queue.empty()){ - Message() << "restricted var order found model" << endl; - }else{ - Message() << "restricted var order missed" << endl; - } - } + d_linEq.directlyAddToCoefficient(inf, v, chg); } } - - if(result == Result::SAT_UNKNOWN && d_queue.empty()){ - result = Result::SAT; - } - - - - d_pivotsInRound.purge(); - // ensure that the conflict variable is still in the queue. - if(d_conflictVariable != ARITHVAR_SENTINEL){ - d_queue.enqueueIfInconsistent(d_conflictVariable); - } - d_conflictVariable = ARITHVAR_SENTINEL; - - d_queue.transitionToCollectionMode(); - Assert(d_queue.inCollectionMode()); - Debug("arith::findModel") << "end findModel() " << instance << " " << result << endl; - return result; - - - // Assert(foundConflict || d_queue.empty()); - - // // Curiously the invariant that we always do a full check - // // means that the assignment we can always empty these queues. - // d_queue.clear(); - // d_pivotsInRound.purge(); - // d_conflictVariable = ARITHVAR_SENTINEL; - - // Assert(!d_queue.inCollectionMode()); - // d_queue.transitionToCollectionMode(); - - - // Assert(d_queue.inCollectionMode()); - - // Debug("arith::findModel") << "end findModel() " << instance << endl; - - // return foundConflict; } -Node SimplexDecisionProcedure::checkBasicForConflict(ArithVar basic){ - - Assert(d_tableau.isBasic(basic)); - const DeltaRational& beta = d_partialModel.getAssignment(basic); - - if(d_partialModel.strictlyLessThanLowerBound(basic, beta)){ - ArithVar x_j = selectSlackUpperBound(basic); - if(x_j == ARITHVAR_SENTINEL ){ - return generateConflictBelowLowerBound(basic); - } - }else if(d_partialModel.strictlyGreaterThanUpperBound(basic, beta)){ - ArithVar x_j = selectSlackLowerBound(basic); - if(x_j == ARITHVAR_SENTINEL ){ - return generateConflictAboveUpperBound(basic); - } - } - return Node::null(); +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); } -//corresponds to Check() in dM06 -//template -bool SimplexDecisionProcedure::searchForFeasibleSolution(uint32_t remainingIterations){ - Debug("arith") << "searchForFeasibleSolution" << endl; - Assert(remainingIterations > 0); - while(remainingIterations > 0){ - if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, const ArithVarVec& set){ + TimerStat::CodeTimer codeTimer(timer); + Assert(!d_errorSet.focusEmpty()); - ArithVar x_i = d_queue.dequeueInconsistentBasicVariable(); - Debug("arith::update::select") << "selectSmallestInconsistentVar()=" << x_i << endl; - if(x_i == ARITHVAR_SENTINEL){ - Debug("arith_update") << "No inconsistent variables" << endl; - return false; //sat - } - - --remainingIterations; - - bool useVarOrderPivot = d_pivotsInRound.count(x_i) >= options::arithPivotThreshold(); - if(!useVarOrderPivot){ - d_pivotsInRound.add(x_i); - } + ArithVar inf = requestVariable(); + Assert(inf != ARITHVAR_SENTINEL); + std::vector coeffs; + std::vector variables; - Debug("playground") << "pivots in rounds: " << d_pivotsInRound.count(x_i) - << " use " << useVarOrderPivot - << " threshold " << options::arithPivotThreshold() - << endl; + for(ArithVarVec::const_iterator iter = set.begin(), iend = set.end(); iter != iend; ++iter){ + ArithVar e = *iter; - PreferenceFunction pf = useVarOrderPivot ? minVarOrder : minBoundAndRowCount; + Assert(d_tableau.isBasic(e)); + Assert(!d_variables.assignmentIsConsistent(e)); - DeltaRational beta_i = d_partialModel.getAssignment(x_i); - ArithVar x_j = ARITHVAR_SENTINEL; - - if(d_partialModel.strictlyLessThanLowerBound(x_i, beta_i)){ - x_j = selectSlackUpperBound(x_i, pf); - if(x_j == ARITHVAR_SENTINEL ){ - ++(d_statistics.d_statUpdateConflicts); - Node conflict = generateConflictBelowLowerBound(x_i); //unsat - d_conflictVariable = x_i; - reportConflict(conflict); - return true; - } - DeltaRational l_i = d_partialModel.getLowerBound(x_i); - d_linEq.pivotAndUpdate(x_i, x_j, l_i); - - }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, beta_i)){ - x_j = selectSlackLowerBound(x_i, pf); - if(x_j == ARITHVAR_SENTINEL ){ - ++(d_statistics.d_statUpdateConflicts); - Node conflict = generateConflictAboveUpperBound(x_i); //unsat - - d_conflictVariable = x_i; - reportConflict(conflict); - return true; - } - DeltaRational u_i = d_partialModel.getUpperBound(x_i); - d_linEq.pivotAndUpdate(x_i, x_j, u_i); - } - Assert(x_j != ARITHVAR_SENTINEL); - - //Check to see if we already have a conflict with x_j to prevent wasteful work - if(CHECK_AFTER_PIVOT){ - Node possibleConflict = checkBasicForConflict(x_j); - if(!possibleConflict.isNull()){ - d_conflictVariable = x_j; - reportConflict(possibleConflict); - return true; // unsat - } - } + int sgn = d_errorSet.getSgn(e); + coeffs.push_back(Rational(sgn)); + variables.push_back(e); } - Assert(remainingIterations == 0); + d_tableau.addRow(inf, coeffs, variables); + DeltaRational newAssignment = d_linEq.computeRowValue(inf, false); + d_variables.setAssignment(inf, newAssignment); - return false; -} - - - -Constraint SimplexDecisionProcedure::weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic){ - - int sgn = coeff.sgn(); - bool ub = aboveUpper?(sgn < 0) : (sgn > 0); - - Constraint c = ub ? - d_partialModel.getUpperBoundConstraint(v) : - d_partialModel.getLowerBoundConstraint(v); - -// #warning "revisit" -// Node exp = ub ? -// d_partialModel.explainUpperBound(v) : -// d_partialModel.explainLowerBound(v); - - bool weakened; - do{ - const DeltaRational& bound = c->getValue(); - - weakened = false; - - Constraint weaker = ub? - c->getStrictlyWeakerUpperBound(true, true): - c->getStrictlyWeakerLowerBound(true, true); - - // Node weaker = ub? - // d_propManager.strictlyWeakerAssertedUpperBound(v, bound): - // d_propManager.strictlyWeakerAssertedLowerBound(v, bound); - - if(weaker != NullConstraint){ - //if(!weaker.isNull()){ - const DeltaRational& weakerBound = weaker->getValue(); - //DeltaRational weakerBound = asDeltaRational(weaker); + d_linEq.trackVariable(inf); - 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; + Debug("Inf") << inf << " " << newAssignment << endl; - Debug("weak") << "found:" << endl; - if(v == basic){ - Debug("weak") << " basic: "; - } - Debug("weak") << " " << surplus << " "<< diff << endl - << " " << bound << c << endl - << " " << weakerBound << weaker << endl; - - Assert(diff > d_DELTA_ZERO); - c = weaker; - } - } - }while(weakened); + return inf; +} - return c; +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer){ + ArithVarVec inError; + d_errorSet.pushFocusInto(inError); + return constructInfeasiblityFunction(timer, inError); } -Node SimplexDecisionProcedure::weakenConflict(bool aboveUpper, ArithVar basicVar){ - TimerStat::CodeTimer codeTimer(d_statistics.d_weakenTime); +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, ArithVar e){ + ArithVarVec justE; + justE.push_back(e); + return constructInfeasiblityFunction(timer, justE); +} - const DeltaRational& assignment = d_partialModel.getAssignment(basicVar); - DeltaRational surplus; - if(aboveUpper){ - Assert(d_partialModel.hasUpperBound(basicVar)); - Assert(assignment > d_partialModel.getUpperBound(basicVar)); - surplus = assignment - d_partialModel.getUpperBound(basicVar); - }else{ - Assert(d_partialModel.hasLowerBound(basicVar)); - Assert(assignment < d_partialModel.getLowerBound(basicVar)); - surplus = d_partialModel.getLowerBound(basicVar) - assignment; - } +void SimplexDecisionProcedure::addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic){ + pair p = make_pair(col, determinizeSgn(sgn)); + sgns[p].push_back(basic); +} - NodeBuilder<> conflict(kind::AND); - bool anyWeakenings = false; - for(Tableau::RowIterator i = d_tableau.basicRowIterator(basicVar); !i.atEnd(); ++i){ +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(); - const Rational& coeff = entry.getCoefficient(); - bool weakening = false; - Constraint c = weakestExplanation(aboveUpper, surplus, v, coeff, weakening, basicVar); - Debug("weak") << "weak : " << weakening << " " - << c->assertedToTheTheory() << " " - << d_partialModel.getAssignment(v) << " " - << c << endl - << c->explainForConflict() << endl; - anyWeakenings = anyWeakenings || weakening; - - Debug("weak") << "weak : " << c->explainForConflict() << endl; - c->explainForConflict(conflict); - } - ++d_statistics.d_weakeningAttempts; - if(anyWeakenings){ - ++d_statistics.d_weakeningSuccesses; + int sgn = (entry.getCoefficient().sgn()); + addSgn(sgns, v, norm * sgn, basic); } - return conflict; } +ArithVar SimplexDecisionProcedure::find_basic_outside(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m){ + pair p = make_pair(col, determinizeSgn(sgn)); + sgn_table::const_iterator i = sgns.find(p); -Node SimplexDecisionProcedure::generateConflictAboveUpperBound(ArithVar conflictVar){ - return weakenConflict(true, conflictVar); + 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(!m.isMember(curr)){ + return curr; + } + } + } + return ARITHVAR_SENTINEL; } -Node SimplexDecisionProcedure::generateConflictBelowLowerBound(ArithVar conflictVar){ - return weakenConflict(false, conflictVar); +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); } - +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/simplex.h b/src/theory/arith/simplex.h index a20920257..bc47a128a 100644 --- a/src/theory/arith/simplex.h +++ b/src/theory/arith/simplex.h @@ -9,7 +9,8 @@ ** See the file COPYING in the top-level source directory for licensing ** information.\endverbatim ** - ** \brief This is an implementation of the Simplex Module for the Simplex for DPLL(T) decision procedure. + ** \brief 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?) @@ -22,19 +23,24 @@ ** 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. + ** 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.) + ** 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 + ** 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. + ** 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. @@ -45,33 +51,34 @@ #include "cvc4_private.h" -#ifndef __CVC4__THEORY__ARITH__SIMPLEX_H -#define __CVC4__THEORY__ARITH__SIMPLEX_H +#pragma once #include "theory/arith/arithvar.h" -#include "theory/arith/arith_priority_queue.h" +#include "theory/arith/error_set.h" #include "theory/arith/delta_rational.h" -#include "theory/arith/matrix.h" +#include "theory/arith/tableau.h" #include "theory/arith/partial_model.h" #include "theory/arith/linear_equality.h" -#include "context/cdlist.h" - #include "util/dense_map.h" -#include "options/options.h" -#include "util/statistics_registry.h" #include "util/result.h" -#include - namespace CVC4 { namespace theory { namespace arith { class SimplexDecisionProcedure { -private: - ArithVar d_conflictVariable; - DenseSet d_successes; +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. */ + ErrorSelectionRule d_heuristicRule; /** Linear equality module. */ LinearEqualityModule& d_linEq; @@ -81,7 +88,7 @@ private: * variables. * Partial model matches that in LinearEqualityModule. */ - ArithPartialModel& d_partialModel; + ArithVariables& d_variables; /** * Stores the linear equalities used by Simplex. @@ -90,30 +97,34 @@ private: Tableau& d_tableau; /** Contains a superset of the basic variables in violation of their bounds. */ - ArithPriorityQueue d_queue; + 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. */ - NodeCallBack& d_conflictChannel; + RaiseConflict d_conflictChannel; - /** Maps a variable to how many times they have been used as a pivot in the simplex search. */ - DenseMultiset d_pivotsInRound; + /** Used for requesting d_opt, bound and error variables for primal.*/ + TempVarMalloc d_arithVarMalloc; - /** Stores to the DeltaRational constant 0. */ - DeltaRational d_DELTA_ZERO; + /** The size of the error set. */ + uint32_t d_errorSize; -public: - SimplexDecisionProcedure(LinearEqualityModule& linEq, NodeCallBack& conflictChannel); + /** A local copy of 0. */ + const Rational d_zero; - /** - * This must be called when the value of a basic variable may now voilate one - * of its bounds. - */ - void updateBasic(ArithVar x){ - d_queue.enqueueIfInconsistent(x); - } + 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 shrinkInfeasFunc(TimerStat& timer, ArithVar inf, const ArithVarVec& dropped); + +public: + SimplexDecisionProcedure(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc); /** * Tries to update the assignments of variables such that all of the @@ -121,7 +132,7 @@ public: * 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 - * false is returned. Otherwise true is returned, and at least 1 conflict + * 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 @@ -129,170 +140,65 @@ public: * * Corresponds to the "check()" procedure in [Cav06]. */ - Result::Sat findModel(bool exactResult); + virtual Result::Sat findModel(bool exactResult) = 0; -private: + void increaseMax() { d_numVariables++; } - /** - * 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. - * - * Both ArithVar must be non-basic in d_tableau. - */ - typedef ArithVar (*PreferenceFunction)(const SimplexDecisionProcedure&, ArithVar, ArithVar); - /** - * minVarOrder is a PreferenceFunction for selecting the smaller of the 2 ArithVars. - * This PreferenceFunction is used during the VarOrder stage of - * findModel. - */ - static ArithVar minVarOrder(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + uint32_t getPivots() const { return d_pivots; } +protected: - /** - * minRowCount 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. - */ - static ArithVar minColLength(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); + /** Reports a conflict to on the output channel. */ + void reportConflict(ArithVar basic); /** - * 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. + * 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. */ - static ArithVar minBoundAndRowCount(const SimplexDecisionProcedure& simp, ArithVar x, ArithVar y); - - - - -private: - bool searchForFeasibleSolution(uint32_t maxIterations); - - enum SearchPeriod {BeforeDiffSearch, DuringDiffSearch, AfterDiffSearch, DuringVarOrderSearch, AfterVarOrderSearch}; - - bool findConflictOnTheQueue(SearchPeriod period); + Node maybeGenerateConflictForBasic(ArithVar basic) const; + /** Returns true if a tracked basic variable has a conflict on it. */ + bool checkBasicForConflict(ArithVar b) const; /** - * 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) - * + * If a basic variable has a conflict on its row, + * this produces a minimized row. */ - template ArithVar selectSlack(ArithVar x_i, PreferenceFunction pf); - ArithVar selectSlackLowerBound(ArithVar x_i, PreferenceFunction pf = minVarOrder) { - return selectSlack(x_i, pf); - } - ArithVar selectSlackUpperBound(ArithVar x_i, PreferenceFunction pf = minVarOrder) { - return selectSlack(x_i, pf); - } - /** - * Returns the smallest basic variable whose assignment is not consistent - * with its upper and lower bounds. - */ - ArithVar selectSmallestInconsistentVar(); + Node generateConflictForBasic(ArithVar basic) const; - /** - * Given a non-basic variable that is know to not be updatable - * to a consistent value, construct and return a conflict. - * Follows section 4.2 in the CAV06 paper. - */ - Node generateConflictAboveUpperBound(ArithVar conflictVar); - Node generateConflictBelowLowerBound(ArithVar conflictVar); -public: - void increaseMax() {d_numVariables++;} - - - void clearQueue() { - d_queue.clear(); + /** Gets a fresh variable from TheoryArith. */ + ArithVar requestVariable(){ + return d_arithVarMalloc.request(); } - - bool debugIsInCollectionQueue(ArithVar var) const{ - Assert(d_queue.inCollectionMode()); - return d_queue.collectionModeContains(var); + /** Releases a requested variable from TheoryArith.*/ + void releaseVariable(ArithVar v){ + d_arithVarMalloc.release(v); } - void reduceQueue(){ - d_queue.reduce(); - } + /** Post condition: !d_queue.moreSignals() */ + bool standardProcessSignals(TimerStat &timer, IntStat& conflictStat); - ArithPriorityQueue::const_iterator queueBegin() const{ - return d_queue.begin(); - } - - ArithPriorityQueue::const_iterator queueEnd() const{ - return d_queue.end(); - } - -private: + 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; + } + }; - /** Reports a conflict to on the output channel. */ - void reportConflict(Node conflict){ - d_conflictChannel(conflict); - ++(d_statistics.d_simplexConflicts); - } + typedef std::hash_map< std::pair, ArithVarVec, ArithVarIntPairHashFunc> sgn_table; - template - inline bool isAcceptableSlack(int sgn, ArithVar nonbasic){ - return - ( above && sgn < 0 && d_partialModel.strictlyBelowUpperBound(nonbasic)) || - ( above && sgn > 0 && d_partialModel.strictlyAboveLowerBound(nonbasic)) || - (!above && sgn > 0 && d_partialModel.strictlyBelowUpperBound(nonbasic)) || - (!above && sgn < 0 && d_partialModel.strictlyAboveLowerBound(nonbasic)); + static inline int determinizeSgn(int sgn){ + return sgn < 0 ? -1 : (sgn == 0 ? 0 : 1); } - /** - * 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. - */ - Node checkBasicForConflict(ArithVar b); - - Node weakenConflict(bool aboveUpper, ArithVar basicVar); - Constraint weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic); - - - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - IntStat d_statUpdateConflicts; - - TimerStat d_findConflictOnTheQueueTime; - - IntStat d_attemptBeforeDiffSearch, d_successBeforeDiffSearch; - IntStat d_attemptAfterDiffSearch, d_successAfterDiffSearch; - IntStat d_attemptDuringDiffSearch, d_successDuringDiffSearch; - IntStat d_attemptDuringVarOrderSearch, d_successDuringVarOrderSearch; - IntStat d_attemptAfterVarOrderSearch, d_successAfterVarOrderSearch; - - IntStat d_weakeningAttempts, d_weakeningSuccesses, d_weakenings; - TimerStat d_weakenTime; - - - IntStat d_simplexConflicts; - - Statistics(); - ~Statistics(); - }; - - Statistics d_statistics; + void addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic); + void addRowSgns(sgn_table& sgns, ArithVar basic, int norm); + ArithVar find_basic_outside(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m); + sgn_table::const_iterator find_sgns(const sgn_table& sgns, ArithVar col, int sgn); };/* class SimplexDecisionProcedure */ @@ -300,5 +206,3 @@ private: }/* CVC4::theory namespace */ }/* CVC4 namespace */ -#endif /* __CVC4__THEORY__ARITH__SIMPLEX_H */ - diff --git a/src/theory/arith/simplex_update.cpp b/src/theory/arith/simplex_update.cpp new file mode 100644 index 000000000..cc3b6a460 --- /dev/null +++ b/src/theory/arith/simplex_update.cpp @@ -0,0 +1,192 @@ +/********************* */ +/*! \file simplex_update.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): none + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief This implements UpdateInfo. + ** + ** This implements the UpdateInfo. + **/ + + +#include "theory/arith/simplex_update.h" +#include "theory/arith/constraint.h" + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + + +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, Constraint 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, Constraint 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.clear(); + updateWitness(); + Assert(unbounded()); + Assert(improvement(d_witness)); + Assert(!describesPivot()); + Assert(debugSgnAgreement()); +} +void UpdateInfo::updatePureFocus(const DeltaRational& delta, Constraint c){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange.clear(); + d_focusDirection = 1; + d_tableauCoefficient.clear(); + updateWitness(); + Assert(!describesPivot()); + Assert(improvement(d_witness)); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, Constraint c){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange.clear(); + d_focusDirection.clear(); + updateWitness(); + Assert(describesPivot()); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, Constraint c, int ec){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection.clear(); + d_tableauCoefficient = &r; + updateWitness(); + Assert(describesPivot()); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::witnessedUpdate(const DeltaRational& delta, Constraint c, int ec, int fd){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection = fd; + d_tableauCoefficient.clear(); + updateWitness(); + Assert(describesPivot() || improvement(d_witness)); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::update(const DeltaRational& delta, const Rational& r, Constraint 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; +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/simplex_update.h b/src/theory/arith/simplex_update.h new file mode 100644 index 000000000..516586568 --- /dev/null +++ b/src/theory/arith/simplex_update.h @@ -0,0 +1,352 @@ +/********************* */ +/*! \file linear_equality.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#pragma once + +#include "theory/arith/delta_rational.h" +#include "theory/arith/arithvar.h" +#include "theory/arith/constraint_forward.h" +#include "util/maybe.h" + +namespace CVC4 { +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. + */ + Maybe 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. */ + Maybe d_errorsChange; + + /** This is the sgn of the change in the value of the focus set.*/ + Maybe d_focusDirection; + + /** This is the sgn of the change in the value of the focus set.*/ + Maybe d_focusChange; + + /** This is the coefficient in the tableau for the entry.*/ + Maybe 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. + */ + Constraint 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.constValue().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, Constraint 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, Constraint 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, Constraint 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, Constraint c, int e); + + /** + * This updates the nonBasicDelta to d, limiting to c, errorChange to e and + * focusDir to f. + */ + void witnessedUpdate(const DeltaRational& d, Constraint c, int e, int f); + void update(const DeltaRational& d, const Rational& r, Constraint c, int e, int f); + + + static UpdateInfo conflict(ArithVar nb, const DeltaRational& delta, const Rational& r, Constraint 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; } + + /** + * If errorsChange has been set, return errorsChange(). + * Otherwise, return def. + */ + inline int errorsChangeSafe(int def) const { + if(d_errorsChange.just()){ + return d_errorsChange; + }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; } + + /** 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(){ + int deltaSgn = d_nonbasicDelta.constValue().sgn(); + setFocusDirection(deltaSgn * d_nonbasicDirection); + } + + /** Requires nonbasicDelta to be set through updateProposal(...). */ + const DeltaRational& nonbasicDelta() const { + return d_nonbasicDelta; + } + const Rational& getCoefficient() const { + Assert(describesPivot()); + Assert(d_tableauCoefficient.constValue() != NULL); + return *(d_tableauCoefficient.constValue()); + } + int basicDirection() const { + return nonbasicDirection() * (getCoefficient().sgn()); + } + + /** Returns the limiting constraint. */ + inline Constraint 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; + } + 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 appropraite 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.just() && d_errorsChange < 0){ + return ErrorDropped; + }else if(d_errorsChange.nothing() || d_errorsChange == 0){ + if(d_focusDirection.just()){ + if(d_focusDirection > 0){ + return FocusImproved; + }else if(d_focusDirection == 0){ + return Degenerate; + } + } + } + return AntiProductive; + } + +}; + +std::ostream& operator<<(std::ostream& out, const UpdateInfo& up); + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/soi_simplex.cpp b/src/theory/arith/soi_simplex.cpp new file mode 100644 index 000000000..f19b13fa5 --- /dev/null +++ b/src/theory/arith/soi_simplex.cpp @@ -0,0 +1,791 @@ +/********************* */ +/*! \file simplex.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + + +#include "theory/arith/soi_simplex.h" +#include "theory/arith/options.h" +#include "theory/arith/constraint.h" + +#include "util/statistics_registry.h" + +using namespace std; + +namespace CVC4 { +namespace theory { +namespace arith { + + +SumOfInfeasibilitiesSPD::SumOfInfeasibilitiesSPD(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(linEq, errors, conflictChannel, tvmalloc) + , d_soiVar(ARITHVAR_SENTINEL) + , d_pivotBudget(0) + , d_prevWitnessImprovement(AntiProductive) + , d_witnessImprovementInARow(0) + , d_sgnDisagreements() + , d_statistics(d_pivots) +{ } + +SumOfInfeasibilitiesSPD::Statistics::Statistics(uint32_t& pivots): + d_initialSignalsTime("theory::arith::SOI::initialProcessTime"), + d_initialConflicts("theory::arith::SOI::UpdateConflicts", 0), + d_soiFoundUnsat("theory::arith::SOI::FoundUnsat", 0), + d_soiFoundSat("theory::arith::SOI::FoundSat", 0), + d_soiMissed("theory::arith::SOI::Missed", 0), + d_soiConflicts("theory::arith::SOI::ConfMin::num", 0), + d_hasToBeMinimal("theory::arith::SOI::HasToBeMin", 0), + d_maybeNotMinimal("theory::arith::SOI::MaybeNotMin", 0), + d_soiTimer("theory::arith::SOI::Time"), + d_soiFocusConstructionTimer("theory::arith::SOI::Construction"), + d_soiConflictMinimization("theory::arith::SOI::Conflict::Minimization"), + d_selectUpdateForSOI("theory::arith::SOI::selectSOI"), + d_finalCheckPivotCounter("theory::arith::SOI::lastPivots", pivots) +{ + StatisticsRegistry::registerStat(&d_initialSignalsTime); + StatisticsRegistry::registerStat(&d_initialConflicts); + + StatisticsRegistry::registerStat(&d_soiFoundUnsat); + StatisticsRegistry::registerStat(&d_soiFoundSat); + StatisticsRegistry::registerStat(&d_soiMissed); + + StatisticsRegistry::registerStat(&d_soiConflicts); + StatisticsRegistry::registerStat(&d_hasToBeMinimal); + StatisticsRegistry::registerStat(&d_maybeNotMinimal); + + StatisticsRegistry::registerStat(&d_soiTimer); + StatisticsRegistry::registerStat(&d_soiFocusConstructionTimer); + + StatisticsRegistry::registerStat(&d_soiConflictMinimization); + + StatisticsRegistry::registerStat(&d_selectUpdateForSOI); + + StatisticsRegistry::registerStat(&d_finalCheckPivotCounter); +} + +SumOfInfeasibilitiesSPD::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_initialSignalsTime); + StatisticsRegistry::unregisterStat(&d_initialConflicts); + + StatisticsRegistry::unregisterStat(&d_soiFoundUnsat); + StatisticsRegistry::unregisterStat(&d_soiFoundSat); + StatisticsRegistry::unregisterStat(&d_soiMissed); + + StatisticsRegistry::unregisterStat(&d_soiConflicts); + StatisticsRegistry::unregisterStat(&d_hasToBeMinimal); + StatisticsRegistry::unregisterStat(&d_maybeNotMinimal); + + StatisticsRegistry::unregisterStat(&d_soiTimer); + StatisticsRegistry::unregisterStat(&d_soiFocusConstructionTimer); + + StatisticsRegistry::unregisterStat(&d_soiConflictMinimization); + + StatisticsRegistry::unregisterStat(&d_selectUpdateForSOI); + StatisticsRegistry::unregisterStat(&d_finalCheckPivotCounter); +} + +Result::Sat SumOfInfeasibilitiesSPD::findModel(bool exactResult){ + Assert(d_conflictVariables.empty()); + Assert(d_sgnDisagreements.empty()); + + d_pivots = 0; + static CVC4_THREADLOCAL(unsigned int) instance = 0; + instance = instance + 1; + static const bool verbose = false; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Debug("soi::findModel") << "soiFindModel("<< instance <<") 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(SUM_METRIC); + + if(initialProcessSignals()){ + d_conflictVariables.purge(); + if(verbose){ Message() << "fcFindModel("<< instance <<") early conflict" << endl; } + Debug("soi::findModel") << "fcFindModel("<< instance <<") early conflict" << endl; + Assert(d_conflictVariables.empty()); + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Debug("soi::findModel") << "fcFindModel("<< instance <<") fixed itself" << endl; + Assert(!d_errorSet.moreSignals()); + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + Debug("soi::findModel") << "fcFindModel(" << instance <<") start non-trivial" << endl; + + exactResult |= options::arithStandardCheckVarOrderPivots() < 0; + + d_prevWitnessImprovement = HeuristicDegenerate; + d_witnessImprovementInARow = 0; + + Result::Sat result = Result::SAT_UNKNOWN; + + if(result == Result::SAT_UNKNOWN){ + if(exactResult){ + d_pivotBudget = -1; + }else{ + d_pivotBudget = options::arithStandardCheckVarOrderPivots(); + } + + result = sumOfInfeasibilities(); + + if(result == Result::UNSAT){ + ++(d_statistics.d_soiFoundUnsat); + if(verbose){ Message() << "fc found unsat";} + }else if(d_errorSet.errorEmpty()){ + ++(d_statistics.d_soiFoundSat); + if(verbose){ Message() << "fc found model"; } + }else{ + ++(d_statistics.d_soiMissed); + if(verbose){ Message() << "fc missed"; } + } + } + if(verbose){ + Message() << "(" << instance << ") pivots " << d_pivots << endl; + } + + Assert(!d_errorSet.moreSignals()); + if(result == Result::SAT_UNKNOWN && d_errorSet.errorEmpty()){ + result = Result::SAT; + } + + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Debug("soi::findModel") << "end findModel() " << instance << " " << 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(); + } + + Debug("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; + + static int instance = 0 ; + ++instance; + + Debug("soi::selectPrimalUpdate") + << "selectPrimalUpdate " << instance << endl + << d_soiVar << " " << d_tableau.basicRowLength(d_soiVar) + << " " << d_linEq._countBounds(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); + + Debug("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); + 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; + +#warning "Who is using computeSafeUpdate?" + LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); + UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); + + Debug("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); + Debug("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{ + Debug("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{ + Debug("updateAndSignal") << "updated basic " << updated; + Debug("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); + Debug("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); + int dir = !d_variables.assignmentIsConsistent(updated) ? + d_errorSet.getSgn(updated) : 0; + Debug("updateAndSignal") << " dir " << dir; + Debug("updateAndSignal") << " _countBounds " << d_linEq._countBounds(updated) << endl; +} + + +void SumOfInfeasibilitiesSPD::updateAndSignal(const UpdateInfo& selected, WitnessImprovement w){ + ArithVar nonbasic = selected.nonbasic(); + + static bool verbose = false; + + Debug("updateAndSignal") << "updateAndSignal " << selected << endl; + + stringstream ss; + if(verbose){ + d_errorSet.debugPrint(ss); + if(selected.describesPivot()){ + ArithVar leaving = selected.leaving(); + ss << "leaving " << leaving + << " " << d_tableau.basicRowLength(leaving) + << " " << d_linEq._countBounds(leaving) + << endl; + } + if(degenerate(w) && selected.describesPivot()){ + ArithVar leaving = selected.leaving(); + Message() + << "degenerate " << leaving + << ", atBounds " << d_linEq.basicsAtBounds(selected) + << ", len " << d_tableau.basicRowLength(leaving) + << ", bc " << d_linEq._countBounds(leaving) + << endl; + } + } + + if(selected.describesPivot()){ + Constraint 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(Debug.isOn("updateAndSignal")){debugPrintSignal(updated);} + if(!d_variables.assignmentIsConsistent(updated)){ + if(checkBasicForConflict(updated)){ + reportConflict(updated); + //Assert(debugUpdatedBasic(selected, updated)); + } + } + }else{ + Debug("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(verbose){ + Message() << "conflict variable " << selected << endl; + Message() << ss.str(); + } + if(Debug.isOn("error")){ d_errorSet.debugPrint(Debug("error")); } + + //Assert(debugSelectedErrorDropped(selected, d_errorSize, d_errorSet.errorSize())); + + adjustFocusAndError(selected, focusChanges); +} + +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(); +} + +unsigned SumOfInfeasibilitiesSPD::tryAllSubsets(const ArithVarVec& set, unsigned depth, ArithVarVec& tmp) { + if(depth < set.size()){ + unsigned resWithout = tryAllSubsets(set, depth+1, tmp); + if(resWithout == tmp.size() && resWithout < set.size()){ + for(unsigned i = 0; i < tmp.size(); ++i){ + cout << tmp[i] << " "; + } + cout << endl; + } + tmp.push_back(set[depth]); + unsigned resWith = tryAllSubsets(set, depth+1, tmp); + if(resWith == tmp.size() && resWith < set.size()){ + for(unsigned i = 0; i < tmp.size(); ++i){ + cout << tmp[i] << " "; + } + cout << endl; + } + tmp.pop_back(); + return std::min(resWith, resWithout); + }else{ + return trySet(tmp); + } +} + +std::vector< ArithVarVec > SumOfInfeasibilitiesSPD::greedyConflictSubsets(){ + std::vector< ArithVarVec > subsets; + Assert(d_soiVar == ARITHVAR_SENTINEL); + + if(d_errorSize <= 2){ + ArithVarVec inError; + d_errorSet.pushFocusInto(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)); + + //cout << "basic error var: " << e << endl; + //d_tableau.debugPrintIsBasic(e); + //d_tableau.printBasicRow(e, cout); + } + + // 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){ + //cout << "found a pair" << endl; + hasParticipated.softAdd(b); + hasParticipated.softAdd(e); + 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; } + + 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); + + bool uniqueChoices = true; + + //cout << "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); + + //cout << "looking for " << nb << " " << oppositeSgn << endl; + + ArithVar basicWithOp = find_basic_outside(sgns, nb, oppositeSgn, hasParticipated); + + if(basicWithOp == ARITHVAR_SENTINEL){ + //cout << "search did not work for " << nb << endl; + // greedy construction has failed + break; + }else{ + //cout << "found " << basicWithOp << endl; + + addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, basicWithOp); + hasParticipated.softAdd(basicWithOp); + underConstruction.push_back(basicWithOp); + } + } + if(spoiler == NULL){ + //cout << "success" << endl; + //then underConstruction contains a conflicting subset + subsets.push_back(underConstruction); + ++d_statistics.d_soiConflicts; + if(underConstruction.size() == 3){ + ++d_statistics.d_hasToBeMinimal; + }else{ + ++d_statistics.d_maybeNotMinimal; + } + }else{ + //cout << "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); + return subsets; +} + +Node SumOfInfeasibilitiesSPD::generateSOIConflict(const ArithVarVec& subset){ + Assert(d_soiVar == ARITHVAR_SENTINEL); + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, subset); + + NodeBuilder<> conflict(kind::AND); + for(ArithVarVec::const_iterator iter = subset.begin(), end = subset.end(); iter != end; ++iter){ + ArithVar e = *iter; + Constraint violated = d_errorSet.getViolated(e); + //cout << "basic error var: " << violated << endl; + violated->explainForConflict(conflict); + + //d_tableau.debugPrintIsBasic(e); + //d_tableau.printBasicRow(e, cout); + } + 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(); + + Constraint c = (coeff.sgn() > 0) ? + d_variables.getUpperBoundConstraint(v) : + d_variables.getLowerBoundConstraint(v); + + //cout << "nb : " << c << endl; + c->explainForConflict(conflict); + } + + Node conf = conflict; + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + return conf; +} + + +WitnessImprovement SumOfInfeasibilitiesSPD::SOIConflict(){ + static int instance = 0; + instance++; + //cout << "SOI conflict " << instance << ": |E| = " << d_errorSize << endl; + //d_errorSet.debugPrint(cout); + //cout << endl; + + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + vector subsets = greedyConflictSubsets(); + Assert( d_soiVar == ARITHVAR_SENTINEL); + + Assert(!subsets.empty()); + for(vector::const_iterator i = subsets.begin(), end = subsets.end(); i != end; ++i){ + const ArithVarVec& subset = *i; + Node conflict = generateSOIConflict(subset); + //cout << conflict << endl; + + //reportConflict(conf); do not do this. We need a custom explanations! + d_conflictChannel(conflict); + } + 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); + + //cout << "SOI conflict " << instance << "end" << endl; + return ConflictFound; +} + +WitnessImprovement SumOfInfeasibilitiesSPD::soiRound() { + Assert(d_soiVar != ARITHVAR_SENTINEL); + + bool useBlands = degeneratePivotsInARow() >= s_maxDegeneratePivotsBeforeBlandsOnLeaving; + LinearEqualityModule::UpdatePreferenceFunction upf = useBlands ? + &LinearEqualityModule::preferWitness: + &LinearEqualityModule::preferWitness; + + LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? + &LinearEqualityModule::minVarOrder : + &LinearEqualityModule::minRowLength; + bpf = &LinearEqualityModule::minVarOrder; + + UpdateInfo selected = selectUpdate(upf, bpf); + + if(selected.uninitialized()){ + Debug("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; + } +} + +bool SumOfInfeasibilitiesSPD::debugSOI(WitnessImprovement w, ostream& out, int instance) const{ +#warning "Redo SOI" + return true; + // out << "DLV("< 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()){ + ++instance; + Debug("dualLike") << "dualLike " << instance << endl; + + Assert(d_errorSet.noSignals()); + // Possible outcomes: + // - conflict + // - budget was exhausted + // - focus went down + Debug("dualLike") << "selectFocusImproving " << endl; + WitnessImprovement w = soiRound(); + + Assert(d_errorSize == d_errorSet.errorSize()); + + if(verbose){ + debugSOI(w, Message(), instance); + } + Assert(debugSOI(w, Debug("dualLike"), instance)); + } + + + 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::SAT_UNKNOWN; + } +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/soi_simplex.h b/src/theory/arith/soi_simplex.h new file mode 100644 index 000000000..1a6becccb --- /dev/null +++ b/src/theory/arith/soi_simplex.h @@ -0,0 +1,228 @@ +/********************* */ +/*! \file simplex.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief 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 "cvc4_private.h" + +#pragma once + +#include "theory/arith/simplex.h" +#include "util/dense_map.h" +#include "util/statistics_registry.h" +#include + +namespace CVC4 { +namespace theory { +namespace arith { + +class SumOfInfeasibilitiesSPD : public SimplexDecisionProcedure { +public: + SumOfInfeasibilitiesSPD(LinearEqualityModule& linEq, ErrorSet& errors, RaiseConflict conflictChannel, TempVarMalloc tvmalloc); + + Result::Sat findModel(bool exactResult); + + // 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::Sat sumOfInfeasibilities(); + + // static const 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); + // } + // } + + int32_t d_pivotBudget; + // enum PivotImprovement { + // ErrorDropped, + // NonDegenerate, + // HeuristicDegenerate, + // BlandsDegenerate + // }; + + WitnessImprovement d_prevWitnessImprovement; + uint32_t d_witnessImprovementInARow; + + uint32_t degeneratePivotsInARow() const; + + static const uint32_t s_focusThreshold = 6; + static const uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; + static const 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; + return useBlands ? + &LinearEqualityModule::preferWitness: + &LinearEqualityModule::preferWitness; + } + + bool debugSOI(WitnessImprovement w, std::ostream& out, int instance) const; + + 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(); + Node 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); + } + 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(uint32_t& pivots); + ~Statistics(); + } d_statistics; +};/* class FCSimplexDecisionProcedure */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/tableau.cpp b/src/theory/arith/tableau.cpp new file mode 100644 index 000000000..c54b0857a --- /dev/null +++ b/src/theory/arith/tableau.cpp @@ -0,0 +1,179 @@ +#include "theory/arith/tableau.h" + +using namespace std; +namespace CVC4 { +namespace theory { +namespace arith { + + +void Tableau::pivot(ArithVar oldBasic, ArithVar newBasic, CoefficientChangeCallback& cb){ + Assert(isBasic(oldBasic)); + Assert(!isBasic(newBasic)); + Assert(d_mergeBuffer.empty()); + + Debug("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.swap(basicOld, basicNew, a_rs_sgn); +} + + + +void Tableau::addRow(ArithVar basic, + const std::vector& coefficients, + const std::vector& variables) +{ + Assert(basic < getNumColumns()); + + 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(Debug.isOn("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(Debug.isOn("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); +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/tableau.h b/src/theory/arith/tableau.h new file mode 100644 index 000000000..8b6ef1df6 --- /dev/null +++ b/src/theory/arith/tableau.h @@ -0,0 +1,143 @@ +#include "cvc4_private.h" + +#pragma once + +#include "util/dense_map.h" +#include "util/rational.h" +#include "theory/arith/arithvar.h" +#include "theory/arith/matrix.h" +#include + +namespace CVC4 { +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)){ + Warning() << v << " is basic." << std::endl; + }else{ + Warning() << 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(rid < d_rowIndex2basic.size()); + return d_rowIndex2basic[rid]; + } + + ColIterator colIterator(ArithVar x) const { + return getColumn(x).begin(); + } + + RowIterator basicRowIterator(ArithVar basic) const { + return getRow(basicToRowIndex(basic)).begin(); + } + + 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 */ + + + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/tableau_sizes.cpp b/src/theory/arith/tableau_sizes.cpp new file mode 100644 index 000000000..7c44f6791 --- /dev/null +++ b/src/theory/arith/tableau_sizes.cpp @@ -0,0 +1,18 @@ +#include "theory/arith/tableau_sizes.h" +#include "theory/arith/tableau.h" + +namespace CVC4 { +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); +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/tableau_sizes.h b/src/theory/arith/tableau_sizes.h new file mode 100644 index 000000000..d6b820300 --- /dev/null +++ b/src/theory/arith/tableau_sizes.h @@ -0,0 +1,28 @@ + +#include "cvc4_private.h" + +#pragma once + +#include +#include "theory/arith/arithvar.h" + +namespace CVC4 { +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 */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/arith/theory_arith.cpp b/src/theory/arith/theory_arith.cpp index bc7b4b278..c0442da90 100644 --- a/src/theory/arith/theory_arith.cpp +++ b/src/theory/arith/theory_arith.cpp @@ -1,11 +1,11 @@ /********************* */ /*! \file theory_arith.cpp ** \verbatim - ** Original author: Tim King + ** Original author: taking ** Major contributors: none - ** Minor contributors (to current version): Kshitij Bansal, Andrew Reynolds, Morgan Deters, Dejan Jovanovic - ** This file is part of the CVC4 project. - ** Copyright (c) 2009-2013 New York University and The University of Iowa + ** Minor contributors (to current version): kshitij, ajreynol, mdeters, dejan + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa ** See the file COPYING in the top-level source directory for licensing ** information.\endverbatim ** @@ -15,35 +15,8 @@ ** \todo document this file **/ -#include "expr/node.h" -#include "expr/kind.h" -#include "expr/metakind.h" -#include "expr/node_builder.h" - -#include "theory/valuation.h" -#include "theory/rewriter.h" - -#include "util/rational.h" -#include "util/integer.h" -#include "util/boolean_simplification.h" -#include "util/dense_map.h" - -#include "smt/logic_exception.h" - -#include "theory/arith/arith_utilities.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/matrix.h" - -#include "theory/arith/arith_rewriter.h" -#include "theory/arith/constraint.h" #include "theory/arith/theory_arith.h" -#include "theory/arith/normal_form.h" -#include "theory/model.h" - -#include "theory/arith/options.h" - -#include +#include "theory/arith/theory_arith_private.h" using namespace std; using namespace CVC4::kind; @@ -52,2516 +25,65 @@ namespace CVC4 { namespace theory { namespace arith { -const uint32_t RESET_START = 2; - - -TheoryArith::TheoryArith(context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo, QuantifiersEngine* qe) : - Theory(THEORY_ARITH, c, u, out, valuation, logicInfo, qe), - d_nlIncomplete( false), - d_qflraStatus(Result::SAT_UNKNOWN), - d_unknownsInARow(0), - d_hasDoneWorkSinceCut(false), - d_learner(u), - d_numberOfVariables(0), - d_pool(), - d_setupLiteralCallback(this), - d_assertionsThatDoNotMatchTheirLiterals(c), - d_nextIntegerCheckVar(0), - d_constantIntegerVariables(c), - d_diseqQueue(c, false), - d_currentPropagationList(), - d_learnedBounds(c), - d_partialModel(c, d_deltaComputeCallback), - d_tableau(), - d_linEq(d_partialModel, d_tableau, d_basicVarModelUpdateCallBack), - d_diosolver(c), - d_restartsCounter(0), - d_tableauSizeHasBeenModified(false), - d_tableauResetDensity(1.6), - d_tableauResetPeriod(10), - d_conflicts(c), - d_raiseConflict(d_conflicts), - d_tempVarMalloc(*this), - d_congruenceManager(c, d_constraintDatabase, d_setupLiteralCallback, d_arithvarNodeMap, d_raiseConflict), - d_simplex(d_linEq, d_raiseConflict), - d_constraintDatabase(c, u, d_arithvarNodeMap, d_congruenceManager, d_raiseConflict), - d_deltaComputeCallback(this), - d_basicVarModelUpdateCallBack(d_simplex), - d_DELTA_ZERO(0), - d_fullCheckCounter(0), - d_cutsInContext(c,0), - d_statistics() -{ -} - -TheoryArith::~TheoryArith(){} - -void TheoryArith::setMasterEqualityEngine(eq::EqualityEngine* eq) { - d_congruenceManager.setMasterEqualityEngine(eq); -} - -Node skolemFunction(const std::string& name, TypeNode dom, TypeNode range){ - NodeManager* currNM = NodeManager::currentNM(); - TypeNode functionType = currNM->mkFunctionType(dom, range); - return currNM->mkSkolem(name, functionType); -} - -Node TheoryArith::getRealDivideBy0Func(){ - Assert(!getLogicInfo().isLinear()); - Assert(getLogicInfo().areRealsUsed()); - - if(d_realDivideBy0Func.isNull()){ - TypeNode realType = NodeManager::currentNM()->realType(); - d_realDivideBy0Func = skolemFunction("/by0_$$", realType, realType); - } - return d_realDivideBy0Func; -} - -Node TheoryArith::getIntDivideBy0Func(){ - Assert(!getLogicInfo().isLinear()); - Assert(getLogicInfo().areIntegersUsed()); - - if(d_intDivideBy0Func.isNull()){ - TypeNode intType = NodeManager::currentNM()->integerType(); - d_intDivideBy0Func = skolemFunction("divby0_$$", intType, intType); - } - return d_intDivideBy0Func; -} - -Node TheoryArith::getIntModulusBy0Func(){ - Assert(!getLogicInfo().isLinear()); - Assert(getLogicInfo().areIntegersUsed()); - - if(d_intModulusBy0Func.isNull()){ - TypeNode intType = NodeManager::currentNM()->integerType(); - d_intModulusBy0Func = skolemFunction("modby0_$$", intType, intType); - } - return d_intModulusBy0Func; -} - -TheoryArith::ModelException::ModelException(TNode n, const char* msg) throw (){ - stringstream ss; - ss << "Cannot construct a model for " << n << " as " << endl << msg; - setMessage(ss.str()); -} -TheoryArith::ModelException::~ModelException() throw (){ } - - -TheoryArith::Statistics::Statistics(): - d_statAssertUpperConflicts("theory::arith::AssertUpperConflicts", 0), - d_statAssertLowerConflicts("theory::arith::AssertLowerConflicts", 0), - d_statUserVariables("theory::arith::UserVariables", 0), - d_statSlackVariables("theory::arith::SlackVariables", 0), - d_statDisequalitySplits("theory::arith::DisequalitySplits", 0), - d_statDisequalityConflicts("theory::arith::DisequalityConflicts", 0), - d_simplifyTimer("theory::arith::simplifyTimer"), - d_staticLearningTimer("theory::arith::staticLearningTimer"), - d_presolveTime("theory::arith::presolveTime"), - d_newPropTime("theory::arith::newPropTimer"), - d_externalBranchAndBounds("theory::arith::externalBranchAndBounds",0), - d_initialTableauSize("theory::arith::initialTableauSize", 0), - d_currSetToSmaller("theory::arith::currSetToSmaller", 0), - d_smallerSetToCurr("theory::arith::smallerSetToCurr", 0), - d_restartTimer("theory::arith::restartTimer"), - d_boundComputationTime("theory::arith::bound::time"), - d_boundComputations("theory::arith::bound::boundComputations",0), - d_boundPropagations("theory::arith::bound::boundPropagations",0), - d_unknownChecks("theory::arith::status::unknowns", 0), - d_maxUnknownsInARow("theory::arith::status::maxUnknownsInARow", 0), - d_avgUnknownsInARow("theory::arith::status::avgUnknownsInARow"), - d_revertsOnConflicts("theory::arith::status::revertsOnConflicts",0), - d_commitsOnConflicts("theory::arith::status::commitsOnConflicts",0), - d_nontrivialSatChecks("theory::arith::status::nontrivialSatChecks",0) -{ - StatisticsRegistry::registerStat(&d_statAssertUpperConflicts); - StatisticsRegistry::registerStat(&d_statAssertLowerConflicts); - - StatisticsRegistry::registerStat(&d_statUserVariables); - StatisticsRegistry::registerStat(&d_statSlackVariables); - StatisticsRegistry::registerStat(&d_statDisequalitySplits); - StatisticsRegistry::registerStat(&d_statDisequalityConflicts); - StatisticsRegistry::registerStat(&d_simplifyTimer); - StatisticsRegistry::registerStat(&d_staticLearningTimer); - - StatisticsRegistry::registerStat(&d_presolveTime); - StatisticsRegistry::registerStat(&d_newPropTime); - - StatisticsRegistry::registerStat(&d_externalBranchAndBounds); - - StatisticsRegistry::registerStat(&d_initialTableauSize); - StatisticsRegistry::registerStat(&d_currSetToSmaller); - StatisticsRegistry::registerStat(&d_smallerSetToCurr); - StatisticsRegistry::registerStat(&d_restartTimer); - - StatisticsRegistry::registerStat(&d_boundComputationTime); - StatisticsRegistry::registerStat(&d_boundComputations); - StatisticsRegistry::registerStat(&d_boundPropagations); - - StatisticsRegistry::registerStat(&d_unknownChecks); - StatisticsRegistry::registerStat(&d_maxUnknownsInARow); - StatisticsRegistry::registerStat(&d_avgUnknownsInARow); - StatisticsRegistry::registerStat(&d_revertsOnConflicts); - StatisticsRegistry::registerStat(&d_commitsOnConflicts); - StatisticsRegistry::registerStat(&d_nontrivialSatChecks); -} - -TheoryArith::Statistics::~Statistics(){ - StatisticsRegistry::unregisterStat(&d_statAssertUpperConflicts); - StatisticsRegistry::unregisterStat(&d_statAssertLowerConflicts); - - StatisticsRegistry::unregisterStat(&d_statUserVariables); - StatisticsRegistry::unregisterStat(&d_statSlackVariables); - StatisticsRegistry::unregisterStat(&d_statDisequalitySplits); - StatisticsRegistry::unregisterStat(&d_statDisequalityConflicts); - StatisticsRegistry::unregisterStat(&d_simplifyTimer); - StatisticsRegistry::unregisterStat(&d_staticLearningTimer); - - StatisticsRegistry::unregisterStat(&d_presolveTime); - StatisticsRegistry::unregisterStat(&d_newPropTime); - - StatisticsRegistry::unregisterStat(&d_externalBranchAndBounds); - - StatisticsRegistry::unregisterStat(&d_initialTableauSize); - StatisticsRegistry::unregisterStat(&d_currSetToSmaller); - StatisticsRegistry::unregisterStat(&d_smallerSetToCurr); - StatisticsRegistry::unregisterStat(&d_restartTimer); - - StatisticsRegistry::unregisterStat(&d_boundComputationTime); - StatisticsRegistry::unregisterStat(&d_boundComputations); - StatisticsRegistry::unregisterStat(&d_boundPropagations); - - StatisticsRegistry::unregisterStat(&d_unknownChecks); - StatisticsRegistry::unregisterStat(&d_maxUnknownsInARow); - StatisticsRegistry::unregisterStat(&d_avgUnknownsInARow); - StatisticsRegistry::unregisterStat(&d_revertsOnConflicts); - StatisticsRegistry::unregisterStat(&d_commitsOnConflicts); - StatisticsRegistry::unregisterStat(&d_nontrivialSatChecks); -} - -void TheoryArith::revertOutOfConflict(){ - d_partialModel.revertAssignmentChanges(); - clearUpdates(); - d_currentPropagationList.clear(); -} - -void TheoryArith::clearUpdates(){ - d_updatedBounds.purge(); -} - -void TheoryArith::zeroDifferenceDetected(ArithVar x){ - Assert(d_congruenceManager.isWatchedVariable(x)); - Assert(d_partialModel.upperBoundIsZero(x)); - Assert(d_partialModel.lowerBoundIsZero(x)); - - Constraint lb = d_partialModel.getLowerBoundConstraint(x); - Constraint 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); - } -} - -/* procedure AssertLower( x_i >= c_i ) */ -bool TheoryArith::AssertLower(Constraint constraint){ - Assert(constraint != NullConstraint); - Assert(constraint->isLowerBound()); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Debug("arith") << "AssertLower(" << x_i << " " << c_i << ")"<< std::endl; - - Assert(!isInteger(x_i) || c_i.isIntegral()); +TheoryArith::TheoryArith(context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo, QuantifiersEngine* qe) + : Theory(THEORY_ARITH, c, u, out, valuation, logicInfo, qe) + , d_internal(new TheoryArithPrivate(*this, c, u, out, valuation, logicInfo, qe)) +{} - //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) - Constraint ubc = d_partialModel.getUpperBoundConstraint(x_i); - Node conflict = ConstraintValue::explainConflict(ubc, constraint); - Debug("arith") << "AssertLower conflict " << conflict << endl; - ++(d_statistics.d_statAssertLowerConflicts); - d_raiseConflict(conflict); - return true; - }else if(cmpToUB == 0){ - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Debug("dio::push") << x_i << endl; - } - Constraint ub = d_partialModel.getUpperBoundConstraint(x_i); - - 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.hasDisequality()){ - Assert(vc.hasEquality()); - const Constraint eq = vc.getEquality(); - const Constraint diseq = vc.getDisequality(); - if(diseq->isTrue()){ - //const Constraint ub = vc.getUpperBound(); - Node conflict = ConstraintValue::explainConflict(diseq, ub, constraint); - - ++(d_statistics.d_statDisequalityConflicts); - Debug("eq") << " assert lower conflict " << conflict << endl; - d_raiseConflict(conflict); - return true; - }else if(!eq->isTrue()){ - Debug("eq") << "lb == ub, propagate eq" << eq << endl; - eq->impliedBy(constraint, d_partialModel.getUpperBoundConstraint(x_i)); - // do not need to add to d_learnedBounds - } - } - }else{ - Assert(cmpToUB < 0); - const ValueCollection& vc = constraint->getValueCollection(); - - if(vc.hasDisequality()){ - const Constraint diseq = vc.getDisequality(); - if(diseq->isTrue()){ - const Constraint ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); - - if(ub->hasProof()){ - Node conflict = ConstraintValue::explainConflict(diseq, ub, constraint); - Debug("eq") << " assert upper conflict " << conflict << endl; - d_raiseConflict(conflict); - return true; - }else if(!ub->negationHasProof()){ - Constraint negUb = ub->getNegation(); - negUb->impliedBy(constraint, diseq); - 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_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(Debug.isOn("model")) { - Debug("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_simplex.updateBasic(x_i); - } - - if(Debug.isOn("model")) { - Debug("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; //sat -} - -/* procedure AssertUpper( x_i <= c_i) */ -bool TheoryArith::AssertUpper(Constraint constraint){ - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Debug("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; - AssertArgument(constraint != NullConstraint, - "AssertUpper() called on a NullConstraint."); - Assert(constraint->isUpperBound()); - - //Too strong because of rounding with integers - //Assert(!constraint->hasLiteral() || original == constraint->getLiteral()); - Assert(!isInteger(x_i) || c_i.isIntegral()); - - Debug("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) - Constraint lbc = d_partialModel.getLowerBoundConstraint(x_i); - Node conflict = ConstraintValue::explainConflict(lbc, constraint); - Debug("arith") << "AssertUpper conflict " << conflict << endl; - ++(d_statistics.d_statAssertUpperConflicts); - d_raiseConflict(conflict); - return true; - }else if(cmpToLB == 0){ // \lowerBound(x_i) == \upperbound(x_i) - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Debug("dio::push") << x_i << endl; - } - Constraint lb = d_partialModel.getLowerBoundConstraint(x_i); - 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); - } - - const ValueCollection& vc = constraint->getValueCollection(); - if(vc.hasDisequality()){ - Assert(vc.hasEquality()); - const Constraint diseq = vc.getDisequality(); - const Constraint eq = vc.getEquality(); - if(diseq->isTrue()){ - Node conflict = ConstraintValue::explainConflict(diseq, lb, constraint); - Debug("eq") << " assert upper conflict " << conflict << endl; - d_raiseConflict(conflict); - return true; - }else if(!eq->isTrue()){ - Debug("eq") << "lb == ub, propagate eq" << eq << endl; - eq->impliedBy(constraint, d_partialModel.getLowerBoundConstraint(x_i)); - //do not bother to add to d_learnedBounds - } - } - }else if(cmpToLB > 0){ - const ValueCollection& vc = constraint->getValueCollection(); - if(vc.hasDisequality()){ - const Constraint diseq = vc.getDisequality(); - if(diseq->isTrue()){ - const Constraint lb = - d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); - if(lb->hasProof()){ - Node conflict = ConstraintValue::explainConflict(diseq, lb, constraint); - Debug("eq") << " assert upper conflict " << conflict << endl; - d_raiseConflict(conflict); - return true; - }else if(!lb->negationHasProof()){ - Constraint negLb = lb->getNegation(); - negLb->impliedBy(constraint, diseq); - //if(!negLb->canBePropagated()){ - d_learnedBounds.push_back(negLb); - //}//otherwise let this be propagated/asserted later - } - } - } - } - - 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_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(Debug.isOn("model")) { - Debug("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_simplex.updateBasic(x_i); - } - - if(Debug.isOn("model")) { - Debug("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; //sat +TheoryArith::~TheoryArith(){ + delete d_internal; } - -/* procedure AssertEquality( x_i == c_i ) */ -bool TheoryArith::AssertEquality(Constraint constraint){ - AssertArgument(constraint != NullConstraint, - "AssertUpper() called on a NullConstraint."); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Debug("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){ - Constraint ubc = d_partialModel.getUpperBoundConstraint(x_i); - Node conflict = ConstraintValue::explainConflict(ubc, constraint); - Debug("arith") << "AssertEquality conflicts with upper bound " << conflict << endl; - d_raiseConflict(conflict); - return true; - } - - if(cmpToLB < 0){ - Constraint lbc = d_partialModel.getLowerBoundConstraint(x_i); - Node conflict = ConstraintValue::explainConflict(lbc, constraint); - Debug("arith") << "AssertEquality conflicts with lower bound" << conflict << endl; - d_raiseConflict(conflict); - return true; - } - - Assert(cmpToUB <= 0); - Assert(cmpToLB >= 0); - Assert(cmpToUB < 0 || cmpToLB > 0); - - - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Debug("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_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(Debug.isOn("model")) { - Debug("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_simplex.updateBasic(x_i); - } - - if(Debug.isOn("model")) { - Debug("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; +void TheoryArith::preRegisterTerm(TNode n){ + d_internal->preRegisterTerm(n); } - -/* procedure AssertDisequality( x_i != c_i ) */ -bool TheoryArith::AssertDisequality(Constraint constraint){ - - AssertArgument(constraint != NullConstraint, - "AssertUpper() called on a NullConstraint."); - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Debug("arith") << "AssertDisequality(" << x_i << " " << c_i << ")"<< std::endl; - - //Should be fine in integers - Assert(!isInteger(x_i) || c_i.isIntegral()); - - 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 Constraint lb = vc.getLowerBound(); - const Constraint ub = vc.getUpperBound(); - if(lb->isTrue() && ub->isTrue()){ - //in conflict - Debug("eq") << "explaining" << endl; - ++(d_statistics.d_statDisequalityConflicts); - Node conflict = ConstraintValue::explainConflict(constraint, lb, ub); - d_raiseConflict(conflict); - return true; - } - } - if(vc.hasLowerBound() ){ - const Constraint lb = vc.getLowerBound(); - if(lb->isTrue()){ - const Constraint ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); - Debug("eq") << "propagate UpperBound " << constraint << lb << ub << endl; - const Constraint negUb = ub->getNegation(); - if(!negUb->isTrue()){ - negUb->impliedBy(constraint, lb); - d_learnedBounds.push_back(negUb); - } - } - } - if(vc.hasUpperBound()){ - const Constraint ub = vc.getUpperBound(); - if(ub->isTrue()){ - const Constraint lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); - - Debug("eq") << "propagate LowerBound " << constraint << lb << ub << endl; - const Constraint negLb = lb->getNegation(); - if(!negLb->isTrue()){ - negLb->impliedBy(constraint, ub); - d_learnedBounds.push_back(negLb); - } - } - } - - bool split = constraint->isSplit(); - - if(!split && c_i == d_partialModel.getAssignment(x_i)){ - Debug("eq") << "lemma now! " << constraint << endl; - d_out->lemma(constraint->split()); - return false; - }else if(d_partialModel.strictlyLessThanLowerBound(x_i, c_i)){ - Debug("eq") << "can drop as less than lb" << constraint << endl; - }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, c_i)){ - Debug("eq") << "can drop as less than ub" << constraint << endl; - }else if(!split){ - Debug("eq") << "push back" << constraint << endl; - d_diseqQueue.push(constraint); - d_partialModel.invalidateDelta(); - }else{ - Debug("eq") << "skipping already split " << constraint << endl; - } - return false; +void TheoryArith::setMasterEqualityEngine(eq::EqualityEngine* eq) { + d_internal->setMasterEqualityEngine(eq); } void TheoryArith::addSharedTerm(TNode n){ - Debug("arith::addSharedTerm") << "addSharedTerm: " << n << endl; - if(n.isConst()){ - d_partialModel.invalidateDelta(); - } - - d_congruenceManager.addSharedTerm(n); - 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()); - } - } - } + d_internal->addSharedTerm(n); } Node TheoryArith::ppRewrite(TNode atom) { - Debug("arith::preprocess") << "arith::preprocess() : " << atom << endl; - - - if (atom.getKind() == kind::EQUAL && options::arithRewriteEq()) { - Node leq = NodeBuilder<2>(kind::LEQ) << atom[0] << atom[1]; - Node geq = NodeBuilder<2>(kind::GEQ) << atom[0] << atom[1]; - Node rewritten = Rewriter::rewrite(leq.andNode(geq)); - Debug("arith::preprocess") << "arith::preprocess() : returning " - << rewritten << endl; - return rewritten; - } else { - return atom; - } + return d_internal->ppRewrite(atom); } Theory::PPAssertStatus TheoryArith::ppAssert(TNode in, SubstitutionMap& outSubstitutions) { - TimerStat::CodeTimer codeTimer(d_statistics.d_simplifyTimer); - Debug("simplify") << "TheoryArith::solve(" << in << ")" << endl; - - - // Solve equalities - Rational minConstant = 0; - Node minMonomial; - Node minVar; - if (in.getKind() == kind::EQUAL) { - Comparison cmp = Comparison::parseNormalForm(in); - - Polynomial left = cmp.getLeft(); - Polynomial right = cmp.getRight(); - - Monomial m = left.getHead(); - if (m.getVarList().singleton()){ - VarList vl = m.getVarList(); - Node var = vl.getNode(); - if (var.getKind() == kind::VARIABLE){ - // 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 == Rewriter::rewrite(elim)); - - - static const unsigned MAX_SUB_SIZE = 2; - if(right.size() > MAX_SUB_SIZE){ - Debug("simplify") << "TheoryArith::solve(): did not substitute due to the right hand side containing too many terms: " << minVar << ":" << elim << endl; - Debug("simplify") << right.size() << endl; - }else if(elim.hasSubterm(minVar)){ - Debug("simplify") << "TheoryArith::solve(): can't substitute due to recursive pattern with sharing: " << minVar << ":" << elim << endl; - }else if (!minVar.getType().isInteger() || right.isIntegral()) { - Assert(!elim.hasSubterm(minVar)); - // cannot eliminate integers here unless we know the resulting - // substitution is integral - Debug("simplify") << "TheoryArith::solve(): substitution " << minVar << " |-> " << elim << endl; - - outSubstitutions.addSubstitution(minVar, elim); - return PP_ASSERT_STATUS_SOLVED; - } else { - Debug("simplify") << "TheoryArith::solve(): can't substitute b/c it's integer: " << 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 PP_ASSERT_STATUS_UNSOLVED; + return d_internal->ppAssert(in, outSubstitutions); } void TheoryArith::ppStaticLearn(TNode n, NodeBuilder<>& learned) { - TimerStat::CodeTimer codeTimer(d_statistics.d_staticLearningTimer); - - d_learner.staticLearning(n, learned); -} - - - -ArithVar TheoryArith::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 TheoryArith::setupVariable(const Variable& x){ - Node n = x.getNode(); - - Assert(!isSetup(n)); - - ++(d_statistics.d_statUserVariables); - requestArithVar(n,false); - //ArithVar varN = requestArithVar(n,false); - //setupInitialValue(varN); - - markSetup(n); - - - if(x.isDivLike()){ - setupDivLike(x); - } - -} - -void TheoryArith::setupVariableList(const VarList& vl){ - Assert(!vl.empty()); - - TNode vlNode = vl.getNode(); - Assert(!isSetup(vlNode)); - Assert(!d_arithvarNodeMap.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(getLogicInfo().isLinear()){ - throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); - } - - d_out->setIncomplete(); - d_nlIncomplete = true; - - ++(d_statistics.d_statUserVariables); - requestArithVar(vlNode, false); - //ArithVar av = requestArithVar(vlNode, false); - //setupInitialValue(av); - - markSetup(vlNode); - } - - /* Note: - * Only call markSetup if the VarList is not a singleton. - * See the comment in setupPolynomail for more. - */ -} - -void TheoryArith::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 TheoryArith::setupDivLike(const Variable& v){ - Assert(v.isDivLike()); - - if(getLogicInfo().isLinear()){ - throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); - } - - Node vnode = v.getNode(); - Assert(isSetup(vnode)); // Otherwise there is some invariant breaking recursion - Polynomial m = Polynomial::parsePolynomial(vnode[0]); - Polynomial n = Polynomial::parsePolynomial(vnode[1]); - - cautiousSetupPolynomial(m); - cautiousSetupPolynomial(n); - - Node lem; - switch(vnode.getKind()){ - case DIVISION: - case INTS_DIVISION: - case INTS_MODULUS: - lem = definingIteForDivLike(vnode); - break; - case DIVISION_TOTAL: - lem = axiomIteForTotalDivision(vnode); - break; - case INTS_DIVISION_TOTAL: - case INTS_MODULUS_TOTAL: - lem = axiomIteForTotalIntDivision(vnode); - break; - default: - /* intentionally blank */ - break; - } - - if(!lem.isNull()){ - Debug("arith::div") << lem << endl; - d_out->lemma(lem); - } -} - -Node TheoryArith::definingIteForDivLike(Node divLike){ - Kind k = divLike.getKind(); - Assert(k == DIVISION || k == INTS_DIVISION || k == INTS_MODULUS); - // (for all ((n Real) (d Real)) - // (= - // (DIVISION n d) - // (ite (= d 0) - // (APPLY [div_0_skolem_function] n) - // (DIVISION_TOTAL x y)))) - - Polynomial n = Polynomial::parsePolynomial(divLike[0]); - Polynomial d = Polynomial::parsePolynomial(divLike[1]); - - NodeManager* currNM = NodeManager::currentNM(); - Node dEq0 = currNM->mkNode(EQUAL, d.getNode(), mkRationalNode(0)); - - Kind kTotal = (k == DIVISION) ? DIVISION_TOTAL : - (k == INTS_DIVISION) ? INTS_DIVISION_TOTAL : INTS_MODULUS_TOTAL; - - Node by0Func = (k == DIVISION) ? getRealDivideBy0Func(): - (k == INTS_DIVISION) ? getIntDivideBy0Func() : getIntModulusBy0Func(); - - - Debug("arith::div") << divLike << endl; - Debug("arith::div") << by0Func << endl; - - Node divTotal = currNM->mkNode(kTotal, n.getNode(), d.getNode()); - Node divZero = currNM->mkNode(APPLY_UF, by0Func, n.getNode()); - - Node defining = divLike.eqNode(dEq0.iteNode( divZero, divTotal)); - - return defining; -} - -Node TheoryArith::axiomIteForTotalDivision(Node div_tot){ - Assert(div_tot.getKind() == DIVISION_TOTAL); - - // Inverse of multiplication axiom: - // (for all ((n Real) (d Real)) - // (ite (= d 0) - // (= (DIVISION_TOTAL n d) 0) - // (= (* d (DIVISION_TOTAL n d)) n))) - - - Polynomial n = Polynomial::parsePolynomial(div_tot[0]); - Polynomial d = Polynomial::parsePolynomial(div_tot[1]); - Polynomial div_tot_p = Polynomial::parsePolynomial(div_tot); - - Comparison invEq = Comparison::mkComparison(EQUAL, n, d * div_tot_p); - Comparison zeroEq = Comparison::mkComparison(EQUAL, div_tot_p, Polynomial::mkZero()); - Node dEq0 = (d.getNode()).eqNode(mkRationalNode(0)); - Node ite = dEq0.iteNode(zeroEq.getNode(), invEq.getNode()); - - return ite; -} - -Node TheoryArith::axiomIteForTotalIntDivision(Node int_div_like){ - Kind k = int_div_like.getKind(); - Assert(k == INTS_DIVISION_TOTAL || k == INTS_MODULUS_TOTAL); - - // (for all ((m Int) (n Int)) - // (=> (distinct n 0) - // (let ((q (div m n)) (r (mod m n))) - // (and (= m (+ (* n q) r)) - // (<= 0 r (- (abs n) 1)))))) - - // Updated for div 0 functions - // (for all ((m Int) (n Int)) - // (let ((q (div m n)) (r (mod m n))) - // (ite (= n 0) - // (and (= q (div_0_func m)) (= r (mod_0_func m))) - // (and (= m (+ (* n q) r)) - // (<= 0 r (- (abs n) 1))))))) - - Polynomial n = Polynomial::parsePolynomial(int_div_like[0]); - Polynomial d = Polynomial::parsePolynomial(int_div_like[1]); - - NodeManager* currNM = NodeManager::currentNM(); - Node zero = mkRationalNode(0); - - Node q = (k == INTS_DIVISION_TOTAL) ? int_div_like : currNM->mkNode(INTS_DIVISION_TOTAL, n.getNode(), d.getNode()); - Node r = (k == INTS_MODULUS_TOTAL) ? int_div_like : currNM->mkNode(INTS_MODULUS_TOTAL, n.getNode(), d.getNode()); - - Node dEq0 = (d.getNode()).eqNode(zero); - Node qEq0 = q.eqNode(zero); - Node rEq0 = r.eqNode(zero); - - Polynomial rp = Polynomial::parsePolynomial(r); - Polynomial qp = Polynomial::parsePolynomial(q); - - Node abs_d = (n.isConstant()) ? - d.getHead().getConstant().abs().getNode() : mkIntSkolem("abs_$$"); - - Node eq = Comparison::mkComparison(EQUAL, n, d * qp + rp).getNode(); - Node leq0 = currNM->mkNode(LEQ, zero, r); - Node leq1 = currNM->mkNode(LT, r, abs_d); - - Node andE = currNM->mkNode(AND, eq, leq0, leq1); - Node defDivMode = dEq0.iteNode(qEq0.andNode(rEq0), andE); - Node lem = abs_d.getMetaKind () == metakind::VARIABLE ? - defDivMode.andNode(d.makeAbsCondition(Variable(abs_d))) : defDivMode; - - return lem; -} - - -void TheoryArith::setupPolynomial(const Polynomial& poly) { - Assert(!poly.containsConstant()); - TNode polyNode = poly.getNode(); - Assert(!isSetup(polyNode)); - Assert(!d_arithvarNodeMap.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() == PLUS){ - d_tableauSizeHasBeenModified = true; - - vector variables; - vector coefficients; - asVectors(poly, coefficients, variables); - - ArithVar varSlack = requestArithVar(polyNode, true); - d_tableau.addRow(varSlack, coefficients, variables); - setupBasicValue(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_statSlackVariables); - markSetup(polyNode); - } - - /* Note: - * It is worth documenting that polyNode should only be marked as - * being setup by this function if it has kind PLUS. - * Other kinds will be marked as being setup by lower levels of setup - * specifically setupVariableList. - */ -} - -void TheoryArith::setupAtom(TNode atom) { - Assert(isRelationOperator(atom.getKind())); - 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 TheoryArith::preRegisterTerm(TNode n) { - Debug("arith::preregister") <<"begin arith::preRegisterTerm("<< n <<")"<< endl; - - if(isRelationOperator(n.getKind())){ - if(!isSetup(n)){ - setupAtom(n); - } - Constraint c = d_constraintDatabase.lookup(n); - Assert(c != NullConstraint); - - Debug("arith::preregister") << "setup constraint" << c << endl; - Assert(!c->canBePropagated()); - c->setPreregistered(); - } - - Debug("arith::preregister") << "end arith::preRegisterTerm("<< n <<")" << endl; -} - -void TheoryArith::releaseArithVar(ArithVar v){ - Assert(d_arithvarNodeMap.hasNode(v)); - - d_constraintDatabase.removeVariable(v); - d_arithvarNodeMap.remove(v); - - d_pool.push_back(v); -} - -ArithVar TheoryArith::requestArithVar(TNode x, bool slack){ - //TODO : The VarList trick is good enough? - Assert(isLeaf(x) || VarList::isMember(x) || x.getKind() == PLUS); - if(getLogicInfo().isLinear() && Variable::isDivMember(x)){ - throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); - } - Assert(!d_arithvarNodeMap.hasArithVar(x)); - Assert(x.getType().isReal());// real or integer - - // ArithVar varX = d_variables.size(); - // d_variables.push_back(Node(x)); - - bool reclaim = !d_pool.empty(); - ArithVar varX; - - if(reclaim){ - varX = d_pool.back(); - d_pool.pop_back(); - - d_partialModel.setAssignment(varX, d_DELTA_ZERO, d_DELTA_ZERO); - }else{ - varX = d_numberOfVariables; - ++d_numberOfVariables; - - d_slackVars.push_back(true); - d_variableTypes.push_back(ATReal); - - d_simplex.increaseMax(); - - d_tableau.increaseSize(); - d_tableauSizeHasBeenModified = true; - - d_partialModel.initialize(varX, d_DELTA_ZERO); - } - - ArithType type; - if(slack){ - //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(x); - type = p.isIntegral() ? ATInteger : ATReal; - }else{ - type = nodeToArithType(x); - } - d_variableTypes[varX] = type; - d_slackVars[varX] = slack; - - d_constraintDatabase.addVariable(varX); - - d_arithvarNodeMap.setArithVar(x,varX); - - // Debug("integers") << "isInteger[[" << x << "]]: " << x.getType().isInteger() << endl; - - // if(slack){ - // //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(x); - // d_variableTypes.push_back(p.isIntegral() ? ATInteger : ATReal); - // }else{ - // d_variableTypes.push_back(nodeToArithType(x)); - // } - - // d_slackVars.push_back(slack); - - // d_simplex.increaseMax(); - - // d_tableau.increaseSize(); - // d_tableauSizeHasBeenModified = true; - - // d_constraintDatabase.addVariable(varX); - - Debug("arith::arithvar") << x << " |-> " << varX << endl; - - Assert(!d_partialModel.hasUpperBound(varX)); - Assert(!d_partialModel.hasLowerBound(varX)); - - return varX; -} - -void TheoryArith::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(); - - Debug("rewriter") << "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_arithvarNodeMap.hasArithVar(n)); - - Assert(d_arithvarNodeMap.hasArithVar(n)); - ArithVar av = d_arithvarNodeMap.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 TheoryArith::setupBasicValue(ArithVar x){ - Assert(d_tableau.isBasic(x)); - - // if(!d_tableau.isBasic(x)){ - // d_partialModel.setAssignment(x, d_DELTA_ZERO, d_DELTA_ZERO); - // }else{ - //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.initialize(x,safeAssignment); - //d_partialModel.setAssignment(x,assignment); - d_partialModel.setAssignment(x,safeAssignment,assignment); - - // } - Debug("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 = Rewriter::rewrite(lemma); - Debug("arith::dio") << "dioCutting found the plane: " << plane.getNode() << endl; - Debug("arith::dio") << "resulting in the cut: " << lemma << endl; - Debug("arith::dio") << "rewritten " << rewrittenLemma << endl; - return rewrittenLemma; - } -} - -Node TheoryArith::callDioSolver(){ - while(!d_constantIntegerVariables.empty()){ - ArithVar v = d_constantIntegerVariables.front(); - d_constantIntegerVariables.pop(); - - Debug("arith::dio") << v << endl; - - Assert(isInteger(v)); - Assert(d_partialModel.boundsAreEqual(v)); - - - Constraint lb = d_partialModel.getLowerBoundConstraint(v); - Constraint ub = d_partialModel.getUpperBoundConstraint(v); - - Node orig = Node::null(); - if(lb->isEquality()){ - orig = lb->explainForConflict(); - }else if(ub->isEquality()){ - orig = ub->explainForConflict(); - }else { - orig = ConstraintValue::explainConflict(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{ - Debug("dio::push") << v << " " << eq.getNode() << " with reason " << orig << endl; - d_diosolver.pushInputConstraint(eq, orig); - } - } - - return d_diosolver.processEquationsForConflict(); -} - -Constraint TheoryArith::constraintFromFactQueue(){ - Assert(!done()); - TNode assertion = get(); - - Kind simpleKind = Comparison::comparisonKind(assertion); - Constraint 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 = Rewriter::rewrite(eq); - if(reEq.getKind() == CONST_BOOLEAN){ - if(reEq.getConst() == isDistinct){ - // if is (not true), or false - Assert((reEq.getConst() && isDistinct) || - (!reEq.getConst() && !isDistinct)); - d_raiseConflict(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){ - Debug("arith::nf") << "getting non-nf assertion " << assertion << " |-> " << reAssertion << endl; - Assert(constraint != NullConstraint); - d_assertionsThatDoNotMatchTheirLiterals.insert(assertion, constraint); - } - } - - // Kind simpleKind = Comparison::comparisonKind(assertion); - // Assert(simpleKind != UNDEFINED_KIND); - // Assert(constraint != NullConstraint || - // simpleKind == EQUAL || - // simpleKind == DISTINCT ); - // if(simpleKind == EQUAL || simpleKind == DISTINCT){ - // Node eq = (simpleKind == DISTINCT) ? assertion[0] : assertion; - - // if(!isSetup(eq)){ - // //The previous code was equivalent to: - // setupAtom(eq); - // constraint = d_constraintDatabase.lookup(assertion); - // } - // } - Assert(constraint != NullConstraint); - - if(constraint->negationHasProof()){ - Constraint negation = constraint->getNegation(); - if(negation->isSelfExplaining()){ - if(Debug.isOn("whytheoryenginewhy")){ - debugPrintFacts(); - } - } - Debug("arith::eq") << constraint << endl; - Debug("arith::eq") << negation << endl; - - NodeBuilder<> nb(kind::AND); - nb << assertion; - negation->explainForConflict(nb); - Node conflict = nb; - Debug("arith::eq") << "conflict" << conflict << endl; - d_raiseConflict(conflict); - return NullConstraint; - } - Assert(!constraint->negationHasProof()); - - if(constraint->assertedToTheTheory()){ - //Do nothing - return NullConstraint; - }else{ - Debug("arith::constraint") << "arith constraint " << constraint << std::endl; - constraint->setAssertedToTheTheory(assertion); - - if(!constraint->hasProof()){ - Debug("arith::constraint") << "marking as constraint as self explaining " << endl; - constraint->selfExplaining(); - }else{ - Debug("arith::constraint") << "already has proof: " << constraint->explainForConflict() << endl; - } - - return constraint; - } -} - -bool TheoryArith::assertionCases(Constraint constraint){ - Assert(constraint->hasProof()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - - switch(constraint->getType()){ - case UpperBound: - if(isInteger(x_i) && constraint->isStrictUpperBound()){ - Constraint floorConstraint = constraint->getFloor(); - if(!floorConstraint->isTrue()){ - if(floorConstraint->negationHasProof()){ - Node conf = ConstraintValue::explainConflict(constraint, floorConstraint->getNegation()); - d_raiseConflict(conf); - return true; - }else{ - floorConstraint->impliedBy(constraint); - // Do not need to add to d_learnedBounds - } - } - return AssertUpper(floorConstraint); - }else{ - return AssertUpper(constraint); - } - case LowerBound: - if(isInteger(x_i) && constraint->isStrictLowerBound()){ - Constraint ceilingConstraint = constraint->getCeiling(); - if(!ceilingConstraint->isTrue()){ - if(ceilingConstraint->negationHasProof()){ - Node conf = ConstraintValue::explainConflict(constraint, ceilingConstraint->getNegation()); - d_raiseConflict(conf); - return true; - } - ceilingConstraint->impliedBy(constraint); - // Do not need to add to learnedBounds - } - return AssertLower(ceilingConstraint); - }else{ - return AssertLower(constraint); - } - case Equality: - return AssertEquality(constraint); - case Disequality: - return AssertDisequality(constraint); - default: - Unreachable(); - return false; - } -} - -/** - * Looks for the next integer variable without an integer assignment in a round robin fashion. - * Changes the value of d_nextIntegerCheckVar. - * - * If this returns false, d_nextIntegerCheckVar does not have an integer assignment. - * If this returns true, all integer variables have an integer assignment. - */ -bool TheoryArith::hasIntegerModel(){ - //if(d_variables.size() > 0){ - if(getNumberOfVariables()){ - const ArithVar rrEnd = d_nextIntegerCheckVar; - do { - //Do not include slack variables - if(isInteger(d_nextIntegerCheckVar) && !isSlackVariable(d_nextIntegerCheckVar)) { // integer - const DeltaRational& d = d_partialModel.getAssignment(d_nextIntegerCheckVar); - if(!d.isIntegral()){ - return false; - } - } - } while((d_nextIntegerCheckVar = (1 + d_nextIntegerCheckVar == getNumberOfVariables() ? 0 : 1 + d_nextIntegerCheckVar)) != rrEnd); - } - return true; -} - -/** Outputs conflicts to the output channel. */ -void TheoryArith::outputConflicts(){ - Assert(!d_conflicts.empty()); - for(size_t i = 0, i_end = d_conflicts.size(); i < i_end; ++i){ - Node conflict = d_conflicts[i]; - Debug("arith::conflict") << "d_conflicts[" << i << "] " << conflict << endl; - d_out->conflict(conflict); - } + d_internal->ppStaticLearn(n, learned); } void TheoryArith::check(Effort effortLevel){ - Assert(d_currentPropagationList.empty()); - Debug("effortlevel") << "TheoryArith::check " << effortLevel << std::endl; - Debug("arith") << "TheoryArith::check begun " << effortLevel << std::endl; - - if(Debug.isOn("arith::consistency")){ - Assert(unenqueuedVariablesAreConsistent()); - } - - bool newFacts = !done(); - //If previous == SAT, then reverts on conflicts are safe - //Otherwise, they are not and must be committed. - Result::Sat previous = d_qflraStatus; - if(newFacts){ - d_qflraStatus = Result::SAT_UNKNOWN; - d_hasDoneWorkSinceCut = true; - } - - while(!done()){ - Constraint curr = constraintFromFactQueue(); - if(curr != NullConstraint){ - bool res CVC4_UNUSED = assertionCases(curr); - Assert(!res || inConflict()); - } - if(inConflict()){ break; } - } - if(!inConflict()){ - while(!d_learnedBounds.empty()){ - // we may attempt some constraints twice. this is okay! - Constraint curr = d_learnedBounds.front(); - d_learnedBounds.pop(); - Debug("arith::learned") << curr << endl; - - bool res CVC4_UNUSED = assertionCases(curr); - Assert(!res || inConflict()); - - if(inConflict()){ break; } - } - } - - if(inConflict()){ - d_qflraStatus = Result::UNSAT; - if(previous == Result::SAT){ - ++d_statistics.d_revertsOnConflicts; - Debug("arith::bt") << "clearing here " << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - revertOutOfConflict(); - d_simplex.clearQueue(); - }else{ - ++d_statistics.d_commitsOnConflicts; - Debug("arith::bt") << "committing here " << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - revertOutOfConflict(); - } - outputConflicts(); - return; - } - - - if(Debug.isOn("arith::print_assertions")) { - debugPrintAssertions(); - } - - bool emmittedConflictOrSplit = false; - Assert(d_conflicts.empty()); - - d_qflraStatus = d_simplex.findModel(fullEffort(effortLevel)); - - switch(d_qflraStatus){ - case Result::SAT: - if(newFacts){ - ++d_statistics.d_nontrivialSatChecks; - } - - Debug("arith::bt") << "committing sap inConflit" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - d_unknownsInARow = 0; - if(Debug.isOn("arith::consistency")){ - Assert(entireStateIsConsistent("sat comit")); - } - break; - case Result::SAT_UNKNOWN: - ++d_unknownsInARow; - ++(d_statistics.d_unknownChecks); - Assert(!fullEffort(effortLevel)); - Debug("arith::bt") << "committing unknown" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - d_statistics.d_maxUnknownsInARow.maxAssign(d_unknownsInARow); - break; - case Result::UNSAT: - d_unknownsInARow = 0; - if(options::revertArithModels() && previous == Result::SAT){ - ++d_statistics.d_revertsOnConflicts; - Debug("arith::bt") << "clearing on conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - revertOutOfConflict(); - d_simplex.clearQueue(); - }else{ - ++d_statistics.d_commitsOnConflicts; - - Debug("arith::bt") << "committing on conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - revertOutOfConflict(); - - if(Debug.isOn("arith::consistency::comitonconflict")){ - entireStateIsConsistent("commit on conflict"); - } - } - outputConflicts(); - emmittedConflictOrSplit = true; - break; - default: - Unimplemented(); - } - d_statistics.d_avgUnknownsInARow.addEntry(d_unknownsInARow); - - // This should be fine if sat or unknown - if(!emmittedConflictOrSplit && - (options::arithPropagationMode() == UNATE_PROP || - options::arithPropagationMode() == BOTH_PROP)){ - TimerStat::CodeTimer codeTimer(d_statistics.d_newPropTime); - Assert(d_qflraStatus != Result::UNSAT); - - while(!d_currentPropagationList.empty() && !inConflict()){ - Constraint 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: - { - Constraint prev = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropLowerBound(curr, prev); - break; - } - case UpperBound: - { - Constraint prev = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropUpperBound(curr, prev); - break; - } - case Equality: - { - Constraint prevLB = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - Constraint prevUB = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropEquality(curr, prevLB, prevUB); - break; - } - default: - Unhandled(curr->getType()); - } - } - - if(inConflict()){ - Debug("arith::unate") << "unate conflict" << endl; - revertOutOfConflict(); - d_qflraStatus = Result::UNSAT; - outputConflicts(); - emmittedConflictOrSplit = true; - Debug("arith::bt") << "committing on unate conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; - - } - }else{ - TimerStat::CodeTimer codeTimer(d_statistics.d_newPropTime); - d_currentPropagationList.clear(); - } - Assert( d_currentPropagationList.empty()); - - if(!emmittedConflictOrSplit && fullEffort(effortLevel)){ - ++d_fullCheckCounter; - } - static const int CUT_ALL_BOUNDED_PERIOD = 10; - if(!emmittedConflictOrSplit && fullEffort(effortLevel) && - d_fullCheckCounter % CUT_ALL_BOUNDED_PERIOD == 1){ - - Debug("arith::demand-restart") << options::maxCutsInContext() << " " << d_cutsInContext << "cut all" << endl; - - vector lemmas = cutAllBounded(); - //output the lemmas - for(vector::const_iterator i = lemmas.begin(); i != lemmas.end(); ++i){ - d_out->lemma(*i); - Debug("arith::demand-restart") << options::maxCutsInContext() << " " << d_cutsInContext << "cut all " << *i << endl; - ++(d_statistics.d_externalBranchAndBounds); - d_cutsInContext = d_cutsInContext + 1; - emmittedConflictOrSplit = lemmas.size() > 0; - } - if(options::maxCutsInContext() <= d_cutsInContext){ - d_out->demandRestart(); - } - } - - if(!emmittedConflictOrSplit && fullEffort(effortLevel)){ - emmittedConflictOrSplit = splitDisequalities(); - } - - if(!emmittedConflictOrSplit && fullEffort(effortLevel) && !hasIntegerModel()){ - Node possibleConflict = Node::null(); - if(!emmittedConflictOrSplit && options::arithDioSolver()){ - possibleConflict = callDioSolver(); - if(possibleConflict != Node::null()){ - revertOutOfConflict(); - Debug("arith::conflict") << "dio conflict " << possibleConflict << endl; - d_out->conflict(possibleConflict); - emmittedConflictOrSplit = true; - } - } - - if(!emmittedConflictOrSplit && d_hasDoneWorkSinceCut && options::arithDioSolver()){ - Node possibleLemma = dioCutting(); - if(!possibleLemma.isNull()){ - Debug("arith") << "dio cut " << possibleLemma << endl; - emmittedConflictOrSplit = true; - d_hasDoneWorkSinceCut = false; - d_out->lemma(possibleLemma); - - d_cutsInContext = d_cutsInContext + 1; - Debug("arith::demand-restart") << options::maxCutsInContext() << " " << d_cutsInContext << " dio cut" << endl; - if(options::maxCutsInContext() <= d_cutsInContext){ - d_out->demandRestart(); - } - } - } - - if(!emmittedConflictOrSplit) { - Node possibleLemma = roundRobinBranch(); - if(!possibleLemma.isNull()){ - - d_cutsInContext = d_cutsInContext + 1; - - Debug("arith::demand-restart") << options::maxCutsInContext() << " " << d_cutsInContext << " branch" << endl; - - ++(d_statistics.d_externalBranchAndBounds); - emmittedConflictOrSplit = true; - d_out->lemma(possibleLemma); - d_cutsInContext = d_cutsInContext + 1; - if(options::maxCutsInContext() <= d_cutsInContext){ - d_out->demandRestart(); - } - } - } - }//if !emmittedConflictOrSplit && fullEffort(effortLevel) && !hasIntegerModel() - if(fullEffort(effortLevel) && d_nlIncomplete){ - // TODO this is total paranoia - d_out->setIncomplete(); - } - - if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - if(Debug.isOn("arith::print_model")) { debugPrintModel(); } - Debug("arith") << "TheoryArith::check end" << std::endl; -} - -Node TheoryArith::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_arithvarNodeMap.asNode(x) << "]] is " << r << "[" << i << "]" << endl; - - Assert(! (r.getDenominator() == 1 && i.getNumerator() == 0)); - Assert(!d.isIntegral()); - TNode var = d_arithvarNodeMap.asNode(x); - Integer floor_d = d.floor(); - - Node ub = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::LEQ, var, mkRationalNode(floor_d))); - Node lb = ub.notNode(); - - - Node lem = NodeManager::currentNM()->mkNode(kind::OR, ub, lb); - Trace("integers") << "integers: branch & bound: " << lem << endl; - if(d_valuation.isSatLiteral(lem[0])) { - Debug("integers") << " " << lem[0] << " == " << d_valuation.getSatValue(lem[0]) << endl; - } else { - Debug("integers") << " " << lem[0] << " is not assigned a SAT literal" << endl; - } - if(d_valuation.isSatLiteral(lem[1])) { - Debug("integers") << " " << lem[1] << " == " << d_valuation.getSatValue(lem[1]) << endl; - } else { - Debug("integers") << " " << lem[1] << " is not assigned a SAT literal" << endl; - } - return lem; + d_internal->check(effortLevel); } -std::vector TheoryArith::cutAllBounded() const{ - vector lemmas; - if(options::doCutAllBounded() && getNumberOfVariables() > 0){ - for(ArithVar iter = 0; iter != getNumberOfVariables(); ++iter){ - //Do not include slack variables - const DeltaRational& d = d_partialModel.getAssignment(iter); - if(isInteger(iter) && !isSlackVariable(iter) && - d_partialModel.hasUpperBound(iter) && - d_partialModel.hasLowerBound(iter) && - !d.isIntegral()){ - Node lem = branchIntegerVariable(iter); - lemmas.push_back(lem); - } - } - } - return lemmas; -} - -/** Returns true if the roundRobinBranching() issues a lemma. */ -Node TheoryArith::roundRobinBranch(){ - if(hasIntegerModel()){ - return Node::null(); - }else{ - ArithVar v = d_nextIntegerCheckVar; - - Assert(isInteger(v)); - Assert(!isSlackVariable(v)); - return branchIntegerVariable(v); - - // Assert(isInteger(v)); - // Assert(!isSlackVariable(v)); - - // const DeltaRational& d = d_partialModel.getAssignment(v); - // const Rational& r = d.getNoninfinitesimalPart(); - // const Rational& i = d.getInfinitesimalPart(); - // Trace("integers") << "integers: assignment to [[" << d_arithvarNodeMap.asNode(v) << "]] is " << r << "[" << i << "]" << endl; - - // Assert(! (r.getDenominator() == 1 && i.getNumerator() == 0)); - // Assert(!d.isIntegral()); - - // TNode var = d_arithvarNodeMap.asNode(v); - // Integer floor_d = d.floor(); - // Integer ceil_d = d.ceiling(); - - // Node leq = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::LEQ, var, mkRationalNode(floor_d))); - // Node geq = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::GEQ, var, mkRationalNode(ceil_d))); - - - // Node lem = NodeManager::currentNM()->mkNode(kind::OR, leq, geq); - // Trace("integers") << "integers: branch & bound: " << lem << endl; - // if(d_valuation.isSatLiteral(lem[0])) { - // Debug("integers") << " " << lem[0] << " == " << d_valuation.getSatValue(lem[0]) << endl; - // } else { - // Debug("integers") << " " << lem[0] << " is not assigned a SAT literal" << endl; - // } - // if(d_valuation.isSatLiteral(lem[1])) { - // Debug("integers") << " " << lem[1] << " == " << d_valuation.getSatValue(lem[1]) << endl; - // } else { - // Debug("integers") << " " << lem[1] << " is not assigned a SAT literal" << endl; - // } - // return lem; - } -} - -bool TheoryArith::splitDisequalities(){ - bool splitSomething = false; - - vector save; - - while(!d_diseqQueue.empty()){ - Constraint front = d_diseqQueue.front(); - d_diseqQueue.pop(); - - if(front->isSplit()){ - Debug("eq") << "split already" << endl; - }else{ - Debug("eq") << "not split already" << endl; - - ArithVar lhsVar = front->getVariable(); - - const DeltaRational& lhsValue = d_partialModel.getAssignment(lhsVar); - const DeltaRational& rhsValue = front->getValue(); - if(lhsValue == rhsValue){ - Debug("arith::lemma") << "Splitting on " << front << endl; - Debug("arith::lemma") << "LHS value = " << lhsValue << endl; - Debug("arith::lemma") << "RHS value = " << rhsValue << endl; - Node lemma = front->split(); - ++(d_statistics.d_statDisequalitySplits); - Node relem = Rewriter::rewrite(lemma); - - Debug("arith::lemma") << "Now " << relem << endl; - d_out->lemma(lemma); - splitSomething = true; - }else if(d_partialModel.strictlyLessThanLowerBound(lhsVar, rhsValue)){ - Debug("eq") << "can drop as less than lb" << front << endl; - }else if(d_partialModel.strictlyGreaterThanUpperBound(lhsVar, rhsValue)){ - Debug("eq") << "can drop as greater than ub" << front << endl; - }else{ - Debug("eq") << "save" << front << ": " <::const_iterator it = d_diseqQueue.begin(); - context::CDQueue::const_iterator it_end = d_diseqQueue.end(); - for(; it != it_end; ++ it) { - Debug("arith::print_assertions") << *it << endl; - } -} - -void TheoryArith::debugPrintModel(){ - Debug("arith::print_model") << "Model:" << endl; - for (var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar i = *vi; - if(d_arithvarNodeMap.hasNode(i)){ - Debug("arith::print_model") << d_arithvarNodeMap.asNode(i) << " : " << - d_partialModel.getAssignment(i); - if(d_tableau.isBasic(i)) - Debug("arith::print_model") << " (basic)"; - Debug("arith::print_model") << endl; - } - } -} - - - Node TheoryArith::explain(TNode n) { - - Debug("arith::explain") << "explain @" << getSatContext()->getLevel() << ": " << n << endl; - - Constraint c = d_constraintDatabase.lookup(n); - if(c != NullConstraint){ - Assert(!c->isSelfExplaining()); - Node exp = c->explainForPropagation(); - Debug("arith::explain") << "constraint explanation" << n << ":" << exp << endl; - return exp; - }else if(d_assertionsThatDoNotMatchTheirLiterals.find(n) != d_assertionsThatDoNotMatchTheirLiterals.end()){ - c = d_assertionsThatDoNotMatchTheirLiterals[n]; - if(!c->isSelfExplaining()){ - Node exp = c->explainForPropagation(); - Debug("arith::explain") << "assertions explanation" << n << ":" << exp << endl; - return exp; - }else{ - Debug("arith::explain") << "this is a strange mismatch" << n << endl; - Assert(d_congruenceManager.canExplain(n)); - Debug("arith::explain") << "this is a strange mismatch" << n << endl; - return d_congruenceManager.explain(n); - } - }else{ - Assert(d_congruenceManager.canExplain(n)); - Debug("arith::explain") << "dm explanation" << n << endl; - return d_congruenceManager.explain(n); - } + return d_internal->explain(n); } - void TheoryArith::propagate(Effort e) { - // This uses model values for safety. Disable for now. - if(d_qflraStatus == Result::SAT && - (options::arithPropagationMode() == BOUND_INFERENCE_PROP || - options::arithPropagationMode() == BOTH_PROP) - && hasAnyUpdates()){ - propagateCandidates(); - }else{ - clearUpdates(); - } - - while(d_constraintDatabase.hasMorePropagations()){ - Constraint c = d_constraintDatabase.nextPropagation(); - Debug("arith::prop") << "next prop" << getSatContext()->getLevel() << ": " << c << endl; - - if(c->negationHasProof()){ - Debug("arith::prop") << "negation has proof " << c->getNegation() << endl - << c->getNegation()->explainForConflict() << 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(); - Debug("arith::prop") << "propagating @" << getSatContext()->getLevel() << " " << literal << endl; - - d_out->propagate(literal); - }else{ - Debug("arith::prop") << "already asserted to the theory " << c->getLiteral() << endl; - } - } - - 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 = Rewriter::rewrite(toProp); - - Constraint constraint = d_constraintDatabase.lookup(normalized); - if(constraint == NullConstraint){ - Debug("arith::prop") << "propagating on non-constraint? " << toProp << endl; - d_out->propagate(toProp); - }else if(constraint->negationHasProof()){ - Node exp = d_congruenceManager.explain(toProp); - Node notNormalized = normalized.getKind() == NOT ? - normalized[0] : normalized.notNode(); - Node lp = flattenAnd(exp.andNode(notNormalized)); - Debug("arith::prop") << "propagate conflict" << lp << endl; - d_out->conflict(lp); - return; - }else{ - Debug("arith::prop") << "propagating still?" << toProp << endl; - - d_out->propagate(toProp); - } - } -} - -DeltaRational TheoryArith::getDeltaValue(TNode n) const throw (DeltaRationalException, ModelException) { - AlwaysAssert(d_qflraStatus != Result::SAT_UNKNOWN); - Debug("arith::value") << n << std::endl; - - switch(n.getKind()) { - - case kind::CONST_RATIONAL: - return n.getConst(); - - case kind::PLUS: { // 2+ args - DeltaRational value(0); - for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { - value = value + getDeltaValue(*i); - } - return value; - } - - case kind::MULT: { // 2+ args - DeltaRational value(1); - unsigned variableParts = 0; - for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { - TNode curr = *i; - value = value * getDeltaValue(curr); - if(!curr.isConst()){ - ++variableParts; - } - } - // TODO: This is a bit of a weak check - if(isSetup(n)){ - ArithVar var = d_arithvarNodeMap.asArithVar(n); - const DeltaRational& assign = d_partialModel.getAssignment(var); - if(assign != value){ - throw ModelException(n, "Model disagrees on non-linear term."); - } - } - return value; - } - case kind::MINUS:{ // 2 args - return getDeltaValue(n[0]) - getDeltaValue(n[1]); - } - - case kind::UMINUS:{ // 1 arg - return (- getDeltaValue(n[0])); - } - - case kind::DIVISION:{ // 2 args - DeltaRational res = getDeltaValue(n[0]) / getDeltaValue(n[1]); - if(isSetup(n)){ - ArithVar var = d_arithvarNodeMap.asArithVar(n); - if(d_partialModel.getAssignment(var) != res){ - throw ModelException(n, "Model disagrees on non-linear term."); - } - } - return res; - } - case kind::DIVISION_TOTAL: - case kind::INTS_DIVISION_TOTAL: - case kind::INTS_MODULUS_TOTAL: { // 2 args - DeltaRational denom = getDeltaValue(n[1]); - if(denom.isZero()){ - return DeltaRational(0,0); - }else{ - DeltaRational numer = getDeltaValue(n[0]); - DeltaRational res; - if(n.getKind() == kind::DIVISION_TOTAL){ - res = numer / denom; - }else if(n.getKind() == kind::INTS_DIVISION_TOTAL){ - res = Rational(numer.euclidianDivideQuotient(denom)); - }else{ - Assert(n.getKind() == kind::INTS_MODULUS_TOTAL); - res = Rational(numer.euclidianDivideRemainder(denom)); - } - if(isSetup(n)){ - ArithVar var = d_arithvarNodeMap.asArithVar(n); - if(d_partialModel.getAssignment(var) != res){ - throw ModelException(n, "Model disagrees on non-linear term."); - } - } - return res; - } - } - - default: - if(isSetup(n)){ - ArithVar var = d_arithvarNodeMap.asArithVar(n); - return d_partialModel.getAssignment(var); - }else{ - throw ModelException(n, "Expected a setup node."); - } - } -} - -Rational TheoryArith::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){ - Constraint curr = *qiter; - - const DeltaRational& rhsValue = curr->getValue(); - relevantDeltaValues.insert(rhsValue); - } - - for(shared_terms_iterator shared_iter = shared_terms_begin(), - shared_end = shared_terms_end(); 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; + d_internal->propagate(e); } void TheoryArith::collectModelInfo( TheoryModel* m, bool fullModel ){ - AlwaysAssert(d_qflraStatus == Result::SAT); - //AlwaysAssert(!d_nlIncomplete, "Arithmetic solver cannot currently produce models for input with nonlinear arithmetic constraints"); - - if(Debug.isOn("arith::collectModelInfo")){ - debugPrintFacts(); - } - - Debug("arith::collectModelInfo") << "collectModelInfo() begin " << endl; - - - // Delta lasts at least the duration of the function call - const Rational& delta = d_partialModel.getDelta(); - std::hash_set shared = currentlySharedTerms(); - - // TODO: - // This is not very good for user push/pop.... - // Revisit when implementing push/pop - for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar v = *vi; - if(!isSlackVariable(v)){ - Node term = d_arithvarNodeMap.asNode(v); - - if(theoryOf(term) == THEORY_ARITH || shared.find(term) != shared.end()){ - const DeltaRational& mod = d_partialModel.getAssignment(v); - Rational qmodel = mod.substituteDelta(delta); - - Node qNode = mkRationalNode(qmodel); - Debug("arith::collectModelInfo") << "m->assertEquality(" << term << ", " << qmodel << ", true)" << endl; - - m->assertEquality(term, qNode, true); - }else{ - Debug("arith::collectModelInfo") << "Skipping m->assertEquality(" << term << ", true)" << endl; - - } - } - } - - // Iterate over equivalence classes in LinearEqualityModule - // const eq::EqualityEngine& ee = d_congruenceManager.getEqualityEngine(); - // m->assertEqualityEngine(&ee); - - Debug("arith::collectModelInfo") << "collectModelInfo() end " << endl; -} - -bool TheoryArith::safeToReset() const { - Assert(!d_tableauSizeHasBeenModified); - - ArithPriorityQueue::const_iterator conf_iter = d_simplex.queueBegin(); - ArithPriorityQueue::const_iterator conf_end = d_simplex.queueEnd(); - for(; conf_iter != conf_end; ++conf_iter){ - ArithVar basic = *conf_iter; - if(!d_smallTableauCopy.isBasic(basic)){ - return false; - } - } - - return true; + d_internal->collectModelInfo(m, fullModel); } void TheoryArith::notifyRestart(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_restartTimer); - - if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - - ++d_restartsCounter; - - uint32_t currSize = d_tableau.size(); - uint32_t copySize = d_smallTableauCopy.size(); - - Debug("arith::reset") << "resetting" << d_restartsCounter << endl; - Debug("arith::reset") << "curr " << currSize << " copy " << copySize << endl; - Debug("arith::reset") << "tableauSizeHasBeenModified " << d_tableauSizeHasBeenModified << endl; - - if(d_tableauSizeHasBeenModified){ - Debug("arith::reset") << "row has been added must copy " << d_restartsCounter << endl; - d_smallTableauCopy = d_tableau; - d_tableauSizeHasBeenModified = false; - }else if( d_restartsCounter >= RESET_START){ - if(copySize >= currSize * 1.1 ){ - Debug("arith::reset") << "size has shrunk " << d_restartsCounter << endl; - ++d_statistics.d_smallerSetToCurr; - d_smallTableauCopy = d_tableau; - }else if(d_tableauResetDensity * copySize <= currSize){ - d_simplex.reduceQueue(); - if(safeToReset()){ - Debug("arith::reset") << "resetting " << d_restartsCounter << endl; - ++d_statistics.d_currSetToSmaller; - d_tableau = d_smallTableauCopy; - }else{ - Debug("arith::reset") << "not safe to reset at the moment " << d_restartsCounter << endl; - } - } - } - Assert(unenqueuedVariablesAreConsistent()); -} - -bool TheoryArith::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_arithvarNodeMap.asArithVar(*i); - if(!d_partialModel.assignmentIsConsistent(var)){ - d_partialModel.printModel(var); - Warning() << s << ":" << "Assignment is not consistent for " << var << d_arithvarNodeMap.asNode(var); - if(d_tableau.isBasic(var)){ - Warning() << " (basic)"; - } - Warning() << endl; - result = false; - } - } - return result; -} - -bool TheoryArith::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_simplex.debugIsInCollectionQueue(var)){ - - d_partialModel.printModel(var); - Warning() << "Unenqueued var is not consistent for " << var << d_arithvarNodeMap.asNode(var); - if(d_tableau.isBasic(var)){ - Warning() << " (basic)"; - } - Warning() << endl; - result = false; - } else if(Debug.isOn("arith::consistency::initial")){ - d_partialModel.printModel(var); - Warning() << "Initial var is not consistent for " << var << d_arithvarNodeMap.asNode(var); - if(d_tableau.isBasic(var)){ - Warning() << " (basic)"; - } - Warning() << endl; - } - } - } - return result; + d_internal->notifyRestart(); } void TheoryArith::presolve(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_presolveTime); - - d_statistics.d_initialTableauSize.setData(d_tableau.size()); - - if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - - static CVC4_THREADLOCAL(unsigned) callCount = 0; - if(Debug.isOn("arith::presolve")) { - Debug("arith::presolve") << "TheoryArith::presolve #" << callCount << endl; - callCount = callCount + 1; - } - - vector lemmas; - switch(options::arithUnateLemmaMode()){ - case NO_PRESOLVE_LEMMAS: - break; - case INEQUALITY_PRESOLVE_LEMMAS: - d_constraintDatabase.outputUnateInequalityLemmas(lemmas); - break; - case EQUALITY_PRESOLVE_LEMMAS: - d_constraintDatabase.outputUnateEqualityLemmas(lemmas); - break; - case ALL_PRESOLVE_LEMMAS: - d_constraintDatabase.outputUnateInequalityLemmas(lemmas); - d_constraintDatabase.outputUnateEqualityLemmas(lemmas); - break; - default: - Unhandled(options::arithUnateLemmaMode()); - } - - vector::const_iterator i = lemmas.begin(), i_end = lemmas.end(); - for(; i != i_end; ++i){ - Node lem = *i; - Debug("arith::oldprop") << " lemma lemma duck " <lemma(lem); - } - - // if(options::arithUnateLemmaMode() == Options::ALL_UNATE){ - // vector lemmas; - // d_constraintDatabase.outputAllUnateLemmas(lemmas); - // vector::const_iterator i = lemmas.begin(), i_end = lemmas.end(); - // for(; i != i_end; ++i){ - // Node lem = *i; - // Debug("arith::oldprop") << " lemma lemma duck " <lemma(lem); - // } - // } + d_internal->presolve(); } EqualityStatus TheoryArith::getEqualityStatus(TNode a, TNode b) { - if(d_qflraStatus == Result::SAT_UNKNOWN){ - return EQUALITY_UNKNOWN; - }else{ - try { - if (getDeltaValue(a) == getDeltaValue(b)) { - return EQUALITY_TRUE_IN_MODEL; - } else { - return EQUALITY_FALSE_IN_MODEL; - } - } catch (DeltaRationalException& dr) { - return EQUALITY_UNKNOWN; - } catch (ModelException& me) { - return EQUALITY_UNKNOWN; - } - } -} - -bool TheoryArith::propagateCandidateBound(ArithVar basic, bool upperBound){ - ++d_statistics.d_boundComputations; - - DeltaRational bound = upperBound ? - d_linEq.computeUpperBound(basic): - d_linEq.computeLowerBound(basic); - - if((upperBound && d_partialModel.strictlyLessThanUpperBound(basic, bound)) || - (!upperBound && d_partialModel.strictlyGreaterThanLowerBound(basic, bound))){ - - // TODO: "Policy point" - //We are only going to recreate the functionality for now. - //In the future this can be improved to generate a temporary constraint - //if none exists. - //Experiment with doing this everytime or only when the new constraint - //implies an unknown fact. - - ConstraintType t = upperBound ? UpperBound : LowerBound; - Constraint bestImplied = d_constraintDatabase.getBestImpliedBound(basic, t, bound); - - // Node bestImplied = upperBound ? - // d_apm.getBestImpliedUpperBound(basic, bound): - // d_apm.getBestImpliedLowerBound(basic, bound); - - if(bestImplied != NullConstraint){ - //This should be stronger - Assert(!upperBound || bound <= bestImplied->getValue()); - Assert(!upperBound || d_partialModel.lessThanUpperBound(basic, bestImplied->getValue())); - - Assert( upperBound || bound >= bestImplied->getValue()); - Assert( upperBound || d_partialModel.greaterThanLowerBound(basic, bestImplied->getValue())); - //slightly changed - - // Constraint c = d_constraintDatabase.lookup(bestImplied); - // Assert(c != NullConstraint); - - bool assertedToTheTheory = bestImplied->assertedToTheTheory(); - bool canBePropagated = bestImplied->canBePropagated(); - bool hasProof = bestImplied->hasProof(); - - Debug("arith::prop") << "arith::prop" << basic - << " " << assertedToTheTheory - << " " << canBePropagated - << " " << hasProof - << endl; - - if(bestImplied->negationHasProof()){ - Warning() << "the negation of " << bestImplied << " : " << endl - << "has proof " << bestImplied->getNegation() << endl - << bestImplied->getNegation()->explainForConflict() << endl; - } - - if(!assertedToTheTheory && canBePropagated && !hasProof ){ - if(upperBound){ - Assert(bestImplied != d_partialModel.getUpperBoundConstraint(basic)); - d_linEq.propagateNonbasicsUpperBound(basic, bestImplied); - }else{ - Assert(bestImplied != d_partialModel.getLowerBoundConstraint(basic)); - d_linEq.propagateNonbasicsLowerBound(basic, bestImplied); - } - // I think this can be skipped if canBePropagated is true - //d_learnedBounds.push(bestImplied); - return true; - } - } - } - return false; -} - -void TheoryArith::propagateCandidate(ArithVar basic){ - bool success = false; - if(d_partialModel.strictlyAboveLowerBound(basic) && d_linEq.hasLowerBounds(basic)){ - success |= propagateCandidateLowerBound(basic); - } - if(d_partialModel.strictlyBelowUpperBound(basic) && d_linEq.hasUpperBounds(basic)){ - success |= propagateCandidateUpperBound(basic); - } - if(success){ - ++d_statistics.d_boundPropagations; - } -} - -void TheoryArith::propagateCandidates(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); - - 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.getRowLength(d_tableau.basicToRowIndex(var)) <= options::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::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); - } + return d_internal->getEqualityStatus(a,b); } }/* CVC4::theory::arith namespace */ diff --git a/src/theory/arith/theory_arith.h b/src/theory/arith/theory_arith.h index 0e5a7e7e4..10c79b293 100644 --- a/src/theory/arith/theory_arith.h +++ b/src/theory/arith/theory_arith.h @@ -10,44 +10,16 @@ ** information.\endverbatim ** ** \brief Arithmetic theory. - ** ** Arithmetic theory. + ** Arithmetic theory. **/ #include "cvc4_private.h" -#ifndef __CVC4__THEORY__ARITH__THEORY_ARITH_H -#define __CVC4__THEORY__ARITH__THEORY_ARITH_H +#pragma once #include "theory/theory.h" -#include "context/context.h" -#include "context/cdlist.h" -#include "context/cdhashset.h" -#include "context/cdinsert_hashmap.h" -#include "context/cdqueue.h" #include "expr/node.h" - -#include "util/dense_map.h" - -#include "theory/arith/arithvar.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/matrix.h" -#include "theory/arith/arith_rewriter.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/simplex.h" -#include "theory/arith/arith_static_learner.h" -#include "theory/arith/arithvar_node_map.h" -#include "theory/arith/dio_solver.h" -#include "theory/arith/congruence_manager.h" - -#include "theory/arith/constraint.h" - -#include "util/statistics_registry.h" -#include "util/result.h" - -#include -#include -#include +#include "theory/arith/theory_arith_private_forward.h" namespace CVC4 { namespace theory { @@ -64,292 +36,12 @@ namespace arith { * http://research.microsoft.com/en-us/um/people/leonardo/cav06.pdf */ class TheoryArith : public Theory { - friend class quantifiers::InstStrategySimplex; private: - bool d_nlIncomplete; - // TODO A better would be: - //context::CDO d_nlIncomplete; - - enum Result::Sat 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; - - - /** - * 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; - - - ArithVar d_numberOfVariables; - inline ArithVar getNumberOfVariables() const { return d_numberOfVariables; } - std::vector d_pool; - void releaseArithVar(ArithVar v); - - /** - * The map between arith variables to nodes. - */ - ArithVarNodeMap d_arithvarNodeMap; - - typedef ArithVarNodeMap::var_iterator var_iterator; - var_iterator var_begin() const { return d_arithvarNodeMap.var_begin(); } - var_iterator var_end() const { return d_arithvarNodeMap.var_end(); } - - NodeSet d_setupNodes; - bool isSetup(Node n) const { - return d_setupNodes.find(n) != d_setupNodes.end(); - } - void markSetup(Node n){ - Assert(!isSetup(n)); - d_setupNodes.insert(n); - } - - void setupDivLike(const Variable& x); - - void setupVariable(const Variable& x); - void setupVariableList(const VarList& vl); - void setupPolynomial(const Polynomial& poly); - void setupAtom(TNode atom); - - void cautiousSetupPolynomial(const Polynomial& p); - - class SetupLiteralCallBack : public TNodeCallBack { - private: - TheoryArith* d_arith; - public: - SetupLiteralCallBack(TheoryArith* ta) : d_arith(ta){} - void operator()(TNode lit){ - TNode atom = (lit.getKind() == kind::NOT) ? lit[0] : lit; - if(!d_arith->isSetup(atom)){ - d_arith->setupAtom(atom); - } - } - } d_setupLiteralCallback; - - /** - * 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; - - /** - * (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 ArithType - { - ATReal = 0, - ATInteger = 1 - }; - - std::vector d_variableTypes; - inline ArithType nodeToArithType(TNode x) const { - return (x.getType().isInteger() ? ATInteger : ATReal); - } - - /** Returns true if x is of type Integer. */ - inline bool isInteger(ArithVar x) const { - return d_variableTypes[x] >= ATInteger; - } - - /** This is the set of variables initially introduced as slack variables. */ - std::vector d_slackVars; - - /** Returns true if the variable was initially introduced as a slack variable. */ - inline bool isSlackVariable(ArithVar x) const{ - return d_slackVars[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(); - Node 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; - - /** - * Manages information about the assignment and upper and lower bounds on - * variables. - */ - ArithPartialModel d_partialModel; - - /** - * 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 const uint32_t s_TABLEAU_RESET_INCREMENT = 5; - - - /** This is only used by simplex at the moment. */ - context::CDList d_conflicts; - class PushCallBack : public NodeCallBack { - private: - context::CDList& d_list; - public: - PushCallBack(context::CDList& l) - : d_list(l) - {} - void operator()(Node n){ - d_list.push_back(n); - } - } d_raiseConflict; - - /** Returns true iff a conflict has been raised. */ - inline bool inConflict() const { - return !d_conflicts.empty(); - } - /** - * Outputs the contents of d_conflicts onto d_out. - * Must be inConflict(). - */ - void outputConflicts(); - - - class TempVarMalloc : public ArithVarMalloc { - private: - TheoryArith& d_ta; - public: - TempVarMalloc(TheoryArith& ta) : d_ta(ta) {} - ArithVar request(){ - Node skolem = mkRealSkolem("tmpVar"); - return d_ta.requestArithVar(skolem, false); - } - void release(ArithVar v){ d_ta.releaseArithVar(v); } - } d_tempVarMalloc; - - /** - * 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; - - /** This implements the Simplex decision procedure. */ - SimplexDecisionProcedure d_simplex; - - - /** The constraint database associated with the theory. */ - ConstraintDatabase d_constraintDatabase; - - class ModelException : public Exception { - public: - ModelException(TNode n, const char* msg) throw (); - virtual ~ModelException() throw (); - }; - - /** Internal model value for the node */ - DeltaRational getDeltaValue(TNode n) const throw (DeltaRationalException, ModelException); - - /** Uninterpretted function symbol for use when interpreting - * division by zero. - */ - Node d_realDivideBy0Func; - Node d_intDivideBy0Func; - Node d_intModulusBy0Func; - Node getRealDivideBy0Func(); - Node getIntDivideBy0Func(); - Node getIntModulusBy0Func(); - - Node definingIteForDivLike(Node divLike); - Node axiomIteForTotalDivision(Node div_tot); - Node axiomIteForTotalIntDivision(Node int_div_like); - + friend class quantifiers::InstStrategySimplex; + friend class TheoryArithPrivate; - class DeltaComputeCallback : public RationalCallBack { - private: - const TheoryArith* d_ta; - public: - DeltaComputeCallback(const TheoryArith* ta) : d_ta(ta){} + TheoryArithPrivate* d_internal; - Rational operator()() const{ - return d_ta->deltaValueForTotalOrder(); - } - } d_deltaComputeCallback; public: TheoryArith(context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo, QuantifiersEngine* qe); @@ -366,7 +58,6 @@ public: void propagate(Effort e); Node explain(TNode n); - Rational deltaValueForTotalOrder() const; void collectModelInfo( TheoryModel* m, bool fullModel ); void shutdown(){ } @@ -382,212 +73,8 @@ public: EqualityStatus getEqualityStatus(TNode a, TNode b); void addSharedTerm(TNode n); - -private: - - class BasicVarModelUpdateCallBack : public ArithVarCallBack{ - private: - SimplexDecisionProcedure& d_simplex; - - public: - BasicVarModelUpdateCallBack(SimplexDecisionProcedure& s): - d_simplex(s) - {} - - void operator()(ArithVar x){ - d_simplex.updateBasic(x); - } - }; - - BasicVarModelUpdateCallBack d_basicVarModelUpdateCallBack; - - /** 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. - * - * If this returns false, d_nextIntegerCheckVar does not have an integer assignment. - * If this returns true, all integer variables have an integer assignment. - */ - bool hasIntegerModel(); - - /** - * Issues branches for non-slack integer variables with non-integer assignments. - * Returns a cut for a lemma. - * If there is an integer model, this returns Node::null(). - */ - Node roundRobinBranch(); - - /** - * 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. - */ - ArithVar requestArithVar(TNode x, bool slack); - - /** Initial (not context dependent) sets up for a variable.*/ - void setupBasicValue(ArithVar x); - - /** Initial (not context dependent) sets up for a new slack variable.*/ - void setupSlack(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(Constraint constraint); - bool AssertUpper(Constraint constraint); - bool AssertEquality(Constraint constraint); - bool AssertDisequality(Constraint 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; - - bool hasAnyUpdates() { return !d_updatedBounds.empty(); } - void clearUpdates(); - - void revertOutOfConflict(); - - 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. - */ - Constraint constraintFromFactQueue(); - bool assertionCases(Constraint 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(); - /** Debugging only routine. Prints the model. */ - void debugPrintModel(); - - /** Counts the number of fullCheck calls to arithmetic. */ - uint32_t d_fullCheckCounter; - std::vector cutAllBounded() const; - Node branchIntegerVariable(ArithVar x) const; - - context::CDO d_cutsInContext; - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - IntStat d_statAssertUpperConflicts, d_statAssertLowerConflicts; - - IntStat d_statUserVariables, d_statSlackVariables; - 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; - - Statistics(); - ~Statistics(); - }; - - Statistics d_statistics; - - };/* class TheoryArith */ }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ - -#endif /* __CVC4__THEORY__ARITH__THEORY_ARITH_H */ diff --git a/src/theory/arith/theory_arith_private.cpp b/src/theory/arith/theory_arith_private.cpp new file mode 100644 index 000000000..263f9536b --- /dev/null +++ b/src/theory/arith/theory_arith_private.cpp @@ -0,0 +1,2692 @@ +/********************* */ +/*! \file theory_arith.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, ajreynol, mdeters, dejan + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + +#include "theory/arith/theory_arith_private.h" + +#include "expr/node.h" +#include "expr/kind.h" +#include "expr/metakind.h" +#include "expr/node_builder.h" + +#include "context/context.h" +#include "context/cdlist.h" +#include "context/cdhashset.h" +#include "context/cdinsert_hashmap.h" +#include "context/cdqueue.h" + +#include "theory/valuation.h" +#include "theory/rewriter.h" + +#include "util/rational.h" +#include "util/integer.h" +#include "util/boolean_simplification.h" +#include "util/dense_map.h" +#include "util/statistics_registry.h" +#include "util/result.h" + +#include "smt/logic_exception.h" + +#include "theory/arith/arithvar.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/matrix.h" +#include "theory/arith/arith_rewriter.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/linear_equality.h" +#include "theory/arith/simplex.h" +#include "theory/arith/arith_static_learner.h" +#include "theory/arith/dio_solver.h" +#include "theory/arith/congruence_manager.h" + +#include "theory/arith/approx_simplex.h" +#include "theory/arith/constraint.h" + +#include "theory/arith/arith_utilities.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/matrix.h" + +#include "theory/arith/arith_rewriter.h" +#include "theory/arith/constraint.h" +#include "theory/arith/theory_arith.h" +#include "theory/arith/normal_form.h" +#include "theory/model.h" + +#include "theory/arith/options.h" + +#include + +#include +#include +#include + +using namespace std; +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { + +TheoryArithPrivate::TheoryArithPrivate(TheoryArith& containing, context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo, QuantifiersEngine* qe) : + d_containing(containing), + d_nlIncomplete( false), + d_boundTracking(), + d_constraintDatabase(c, u, d_partialModel, d_congruenceManager, RaiseConflict(*this)), + d_qflraStatus(Result::SAT_UNKNOWN), + d_unknownsInARow(0), + d_hasDoneWorkSinceCut(false), + d_learner(u), + d_assertionsThatDoNotMatchTheirLiterals(c), + d_nextIntegerCheckVar(0), + d_constantIntegerVariables(c), + d_diseqQueue(c, false), + d_currentPropagationList(), + d_learnedBounds(c), + d_partialModel(c, DeltaComputeCallback(*this)), + d_errorSet(d_partialModel, TableauSizes(&d_tableau), BoundCountingLookup(&d_boundTracking)), + d_tableau(), + d_linEq(d_partialModel, d_tableau, d_boundTracking, BasicVarModelUpdateCallBack(*this)), + d_diosolver(c), + d_restartsCounter(0), + d_tableauSizeHasBeenModified(false), + d_tableauResetDensity(1.6), + d_tableauResetPeriod(10), + d_conflicts(c), + d_congruenceManager(c, d_constraintDatabase, SetupLiteralCallBack(*this), d_partialModel, RaiseConflict(*this)), + d_dualSimplex(d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_pureUpdate(d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_fcSimplex(d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_soiSimplex(d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_DELTA_ZERO(0), + d_fullCheckCounter(0), + d_cutCount(c, 0), + d_cutInContext(c), + d_statistics() +{ +} + +TheoryArithPrivate::~TheoryArithPrivate(){ } + + +void TheoryArithPrivate::setMasterEqualityEngine(eq::EqualityEngine* eq) { + d_congruenceManager.setMasterEqualityEngine(eq); +} + +Node TheoryArithPrivate::getRealDivideBy0Func(){ + Assert(!getLogicInfo().isLinear()); + Assert(getLogicInfo().areRealsUsed()); + + if(d_realDivideBy0Func.isNull()){ + TypeNode realType = NodeManager::currentNM()->realType(); + d_realDivideBy0Func = skolemFunction("/by0_$$", realType, realType); + } + return d_realDivideBy0Func; +} + +Node TheoryArithPrivate::getIntDivideBy0Func(){ + Assert(!getLogicInfo().isLinear()); + Assert(getLogicInfo().areIntegersUsed()); + + if(d_intDivideBy0Func.isNull()){ + TypeNode intType = NodeManager::currentNM()->integerType(); + d_intDivideBy0Func = skolemFunction("divby0_$$", intType, intType); + } + return d_intDivideBy0Func; +} + +Node TheoryArithPrivate::getIntModulusBy0Func(){ + Assert(!getLogicInfo().isLinear()); + Assert(getLogicInfo().areIntegersUsed()); + + if(d_intModulusBy0Func.isNull()){ + TypeNode intType = NodeManager::currentNM()->integerType(); + d_intModulusBy0Func = skolemFunction("modby0_$$", intType, intType); + } + return d_intModulusBy0Func; +} + +TheoryArithPrivate::ModelException::ModelException(TNode n, const char* msg) throw (){ + stringstream ss; + ss << "Cannot construct a model for " << n << " as " << endl << msg; + setMessage(ss.str()); +} +TheoryArithPrivate::ModelException::~ModelException() throw (){ } + + +TheoryArithPrivate::Statistics::Statistics(): + d_statAssertUpperConflicts("theory::arith::AssertUpperConflicts", 0), + d_statAssertLowerConflicts("theory::arith::AssertLowerConflicts", 0), + d_statUserVariables("theory::arith::UserVariables", 0), + d_statSlackVariables("theory::arith::SlackVariables", 0), + d_statDisequalitySplits("theory::arith::DisequalitySplits", 0), + d_statDisequalityConflicts("theory::arith::DisequalityConflicts", 0), + d_simplifyTimer("theory::arith::simplifyTimer"), + d_staticLearningTimer("theory::arith::staticLearningTimer"), + d_presolveTime("theory::arith::presolveTime"), + d_newPropTime("theory::arith::newPropTimer"), + d_externalBranchAndBounds("theory::arith::externalBranchAndBounds",0), + d_initialTableauSize("theory::arith::initialTableauSize", 0), + d_currSetToSmaller("theory::arith::currSetToSmaller", 0), + d_smallerSetToCurr("theory::arith::smallerSetToCurr", 0), + d_restartTimer("theory::arith::restartTimer"), + d_boundComputationTime("theory::arith::bound::time"), + d_boundComputations("theory::arith::bound::boundComputations",0), + d_boundPropagations("theory::arith::bound::boundPropagations",0), + d_unknownChecks("theory::arith::status::unknowns", 0), + d_maxUnknownsInARow("theory::arith::status::maxUnknownsInARow", 0), + d_avgUnknownsInARow("theory::arith::status::avgUnknownsInARow"), + d_revertsOnConflicts("theory::arith::status::revertsOnConflicts",0), + d_commitsOnConflicts("theory::arith::status::commitsOnConflicts",0), + d_nontrivialSatChecks("theory::arith::status::nontrivialSatChecks",0), + d_satPivots("pivots::sat"), + d_unsatPivots("pivots::unsat"), + d_unknownPivots("pivots::unkown") +{ + StatisticsRegistry::registerStat(&d_statAssertUpperConflicts); + StatisticsRegistry::registerStat(&d_statAssertLowerConflicts); + + StatisticsRegistry::registerStat(&d_statUserVariables); + StatisticsRegistry::registerStat(&d_statSlackVariables); + StatisticsRegistry::registerStat(&d_statDisequalitySplits); + StatisticsRegistry::registerStat(&d_statDisequalityConflicts); + StatisticsRegistry::registerStat(&d_simplifyTimer); + StatisticsRegistry::registerStat(&d_staticLearningTimer); + + StatisticsRegistry::registerStat(&d_presolveTime); + StatisticsRegistry::registerStat(&d_newPropTime); + + StatisticsRegistry::registerStat(&d_externalBranchAndBounds); + + StatisticsRegistry::registerStat(&d_initialTableauSize); + StatisticsRegistry::registerStat(&d_currSetToSmaller); + StatisticsRegistry::registerStat(&d_smallerSetToCurr); + StatisticsRegistry::registerStat(&d_restartTimer); + + StatisticsRegistry::registerStat(&d_boundComputationTime); + StatisticsRegistry::registerStat(&d_boundComputations); + StatisticsRegistry::registerStat(&d_boundPropagations); + + StatisticsRegistry::registerStat(&d_unknownChecks); + StatisticsRegistry::registerStat(&d_maxUnknownsInARow); + StatisticsRegistry::registerStat(&d_avgUnknownsInARow); + StatisticsRegistry::registerStat(&d_revertsOnConflicts); + StatisticsRegistry::registerStat(&d_commitsOnConflicts); + StatisticsRegistry::registerStat(&d_nontrivialSatChecks); + + + StatisticsRegistry::registerStat(&d_satPivots); + StatisticsRegistry::registerStat(&d_unsatPivots); + StatisticsRegistry::registerStat(&d_unknownPivots); +} + +TheoryArithPrivate::Statistics::~Statistics(){ + StatisticsRegistry::unregisterStat(&d_statAssertUpperConflicts); + StatisticsRegistry::unregisterStat(&d_statAssertLowerConflicts); + + StatisticsRegistry::unregisterStat(&d_statUserVariables); + StatisticsRegistry::unregisterStat(&d_statSlackVariables); + StatisticsRegistry::unregisterStat(&d_statDisequalitySplits); + StatisticsRegistry::unregisterStat(&d_statDisequalityConflicts); + StatisticsRegistry::unregisterStat(&d_simplifyTimer); + StatisticsRegistry::unregisterStat(&d_staticLearningTimer); + + StatisticsRegistry::unregisterStat(&d_presolveTime); + StatisticsRegistry::unregisterStat(&d_newPropTime); + + StatisticsRegistry::unregisterStat(&d_externalBranchAndBounds); + + StatisticsRegistry::unregisterStat(&d_initialTableauSize); + StatisticsRegistry::unregisterStat(&d_currSetToSmaller); + StatisticsRegistry::unregisterStat(&d_smallerSetToCurr); + StatisticsRegistry::unregisterStat(&d_restartTimer); + + StatisticsRegistry::unregisterStat(&d_boundComputationTime); + StatisticsRegistry::unregisterStat(&d_boundComputations); + StatisticsRegistry::unregisterStat(&d_boundPropagations); + + StatisticsRegistry::unregisterStat(&d_unknownChecks); + StatisticsRegistry::unregisterStat(&d_maxUnknownsInARow); + StatisticsRegistry::unregisterStat(&d_avgUnknownsInARow); + StatisticsRegistry::unregisterStat(&d_revertsOnConflicts); + StatisticsRegistry::unregisterStat(&d_commitsOnConflicts); + StatisticsRegistry::unregisterStat(&d_nontrivialSatChecks); + + StatisticsRegistry::unregisterStat(&d_satPivots); + StatisticsRegistry::unregisterStat(&d_unsatPivots); + StatisticsRegistry::unregisterStat(&d_unknownPivots); +} + +void TheoryArithPrivate::revertOutOfConflict(){ + d_partialModel.revertAssignmentChanges(); + clearUpdates(); + d_currentPropagationList.clear(); +} + +void TheoryArithPrivate::clearUpdates(){ + d_updatedBounds.purge(); +} + +void TheoryArithPrivate::zeroDifferenceDetected(ArithVar x){ + Assert(d_congruenceManager.isWatchedVariable(x)); + Assert(d_partialModel.upperBoundIsZero(x)); + Assert(d_partialModel.lowerBoundIsZero(x)); + + Constraint lb = d_partialModel.getLowerBoundConstraint(x); + Constraint 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); + } +} + +/* procedure AssertLower( x_i >= c_i ) */ +bool TheoryArithPrivate::AssertLower(Constraint constraint){ + Assert(constraint != NullConstraint); + Assert(constraint->isLowerBound()); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Debug("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) + Constraint ubc = d_partialModel.getUpperBoundConstraint(x_i); + Node conflict = ConstraintValue::explainConflict(ubc, constraint); + Debug("arith") << "AssertLower conflict " << conflict << endl; + ++(d_statistics.d_statAssertLowerConflicts); + raiseConflict(conflict); + return true; + }else if(cmpToUB == 0){ + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Debug("dio::push") << x_i << endl; + } + Constraint ub = d_partialModel.getUpperBoundConstraint(x_i); + + 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.hasDisequality()){ + Assert(vc.hasEquality()); + const Constraint eq = vc.getEquality(); + const Constraint diseq = vc.getDisequality(); + if(diseq->isTrue()){ + //const Constraint ub = vc.getUpperBound(); + Node conflict = ConstraintValue::explainConflict(diseq, ub, constraint); + + ++(d_statistics.d_statDisequalityConflicts); + Debug("eq") << " assert lower conflict " << conflict << endl; + raiseConflict(conflict); + return true; + }else if(!eq->isTrue()){ + Debug("eq") << "lb == ub, propagate eq" << eq << endl; + eq->impliedBy(constraint, d_partialModel.getUpperBoundConstraint(x_i)); + // do not need to add to d_learnedBounds + } + } + }else{ + Assert(cmpToUB < 0); + const ValueCollection& vc = constraint->getValueCollection(); + + if(vc.hasDisequality()){ + const Constraint diseq = vc.getDisequality(); + if(diseq->isTrue()){ + const Constraint ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); + + if(ub->hasProof()){ + Node conflict = ConstraintValue::explainConflict(diseq, ub, constraint); + Debug("eq") << " assert upper conflict " << conflict << endl; + raiseConflict(conflict); + return true; + }else if(!ub->negationHasProof()){ + Constraint negUb = ub->getNegation(); + negUb->impliedBy(constraint, diseq); + 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_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(Debug.isOn("model")) { + Debug("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(Debug.isOn("model")) { + Debug("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(Constraint constraint){ + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Debug("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; + AssertArgument(constraint != NullConstraint, + "AssertUpper() called on a NullConstraint."); + Assert(constraint->isUpperBound()); + + //Too strong because of rounding with integers + //Assert(!constraint->hasLiteral() || original == constraint->getLiteral()); + Assert(!isInteger(x_i) || c_i.isIntegral()); + + Debug("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) + Constraint lbc = d_partialModel.getLowerBoundConstraint(x_i); + Node conflict = ConstraintValue::explainConflict(lbc, constraint); + Debug("arith") << "AssertUpper conflict " << conflict << endl; + ++(d_statistics.d_statAssertUpperConflicts); + raiseConflict(conflict); + return true; + }else if(cmpToLB == 0){ // \lowerBound(x_i) == \upperbound(x_i) + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Debug("dio::push") << x_i << endl; + } + Constraint lb = d_partialModel.getLowerBoundConstraint(x_i); + 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); + } + + const ValueCollection& vc = constraint->getValueCollection(); + if(vc.hasDisequality()){ + Assert(vc.hasEquality()); + const Constraint diseq = vc.getDisequality(); + const Constraint eq = vc.getEquality(); + if(diseq->isTrue()){ + Node conflict = ConstraintValue::explainConflict(diseq, lb, constraint); + Debug("eq") << " assert upper conflict " << conflict << endl; + raiseConflict(conflict); + return true; + }else if(!eq->isTrue()){ + Debug("eq") << "lb == ub, propagate eq" << eq << endl; + eq->impliedBy(constraint, d_partialModel.getLowerBoundConstraint(x_i)); + //do not bother to add to d_learnedBounds + } + } + }else if(cmpToLB > 0){ + const ValueCollection& vc = constraint->getValueCollection(); + if(vc.hasDisequality()){ + const Constraint diseq = vc.getDisequality(); + if(diseq->isTrue()){ + const Constraint lb = + d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); + if(lb->hasProof()){ + Node conflict = ConstraintValue::explainConflict(diseq, lb, constraint); + Debug("eq") << " assert upper conflict " << conflict << endl; + raiseConflict(conflict); + return true; + }else if(!lb->negationHasProof()){ + Constraint negLb = lb->getNegation(); + negLb->impliedBy(constraint, diseq); + //if(!negLb->canBePropagated()){ + d_learnedBounds.push_back(negLb); + //}//otherwise let this be propagated/asserted later + } + } + } + } + + 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_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(Debug.isOn("model")) { + Debug("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(Debug.isOn("model")) { + Debug("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(Constraint constraint){ + AssertArgument(constraint != NullConstraint, + "AssertUpper() called on a NullConstraint."); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Debug("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){ + Constraint ubc = d_partialModel.getUpperBoundConstraint(x_i); + Node conflict = ConstraintValue::explainConflict(ubc, constraint); + Debug("arith") << "AssertEquality conflicts with upper bound " << conflict << endl; + raiseConflict(conflict); + return true; + } + + if(cmpToLB < 0){ + Constraint lbc = d_partialModel.getLowerBoundConstraint(x_i); + Node conflict = ConstraintValue::explainConflict(lbc, constraint); + Debug("arith") << "AssertEquality conflicts with lower bound" << conflict << endl; + raiseConflict(conflict); + return true; + } + + Assert(cmpToUB <= 0); + Assert(cmpToLB >= 0); + Assert(cmpToUB < 0 || cmpToLB > 0); + + + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Debug("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_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(Debug.isOn("model")) { + Debug("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(Debug.isOn("model")) { + Debug("model") << "after" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + return false; +} + + +/* procedure AssertDisequality( x_i != c_i ) */ +bool TheoryArithPrivate::AssertDisequality(Constraint constraint){ + + AssertArgument(constraint != NullConstraint, + "AssertUpper() called on a NullConstraint."); + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Debug("arith") << "AssertDisequality(" << x_i << " " << c_i << ")"<< std::endl; + + //Should be fine in integers + Assert(!isInteger(x_i) || c_i.isIntegral()); + + 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 Constraint lb = vc.getLowerBound(); + const Constraint ub = vc.getUpperBound(); + if(lb->isTrue() && ub->isTrue()){ + //in conflict + Debug("eq") << "explaining" << endl; + ++(d_statistics.d_statDisequalityConflicts); + Node conflict = ConstraintValue::explainConflict(constraint, lb, ub); + raiseConflict(conflict); + return true; + } + } + if(vc.hasLowerBound() ){ + const Constraint lb = vc.getLowerBound(); + if(lb->isTrue()){ + const Constraint ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); + Debug("eq") << "propagate UpperBound " << constraint << lb << ub << endl; + const Constraint negUb = ub->getNegation(); + if(!negUb->isTrue()){ + negUb->impliedBy(constraint, lb); + d_learnedBounds.push_back(negUb); + } + } + } + if(vc.hasUpperBound()){ + const Constraint ub = vc.getUpperBound(); + if(ub->isTrue()){ + const Constraint lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); + + Debug("eq") << "propagate LowerBound " << constraint << lb << ub << endl; + const Constraint negLb = lb->getNegation(); + if(!negLb->isTrue()){ + negLb->impliedBy(constraint, ub); + d_learnedBounds.push_back(negLb); + } + } + } + + bool split = constraint->isSplit(); + + if(!split && c_i == d_partialModel.getAssignment(x_i)){ + Debug("eq") << "lemma now! " << constraint << endl; + outputLemma(constraint->split()); + return false; + }else if(d_partialModel.strictlyLessThanLowerBound(x_i, c_i)){ + Debug("eq") << "can drop as less than lb" << constraint << endl; + }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, c_i)){ + Debug("eq") << "can drop as less than ub" << constraint << endl; + }else if(!split){ + Debug("eq") << "push back" << constraint << endl; + d_diseqQueue.push(constraint); + d_partialModel.invalidateDelta(); + }else{ + Debug("eq") << "skipping already split " << constraint << endl; + } + return false; +} + +void TheoryArithPrivate::addSharedTerm(TNode n){ + Debug("arith::addSharedTerm") << "addSharedTerm: " << n << endl; + if(n.isConst()){ + d_partialModel.invalidateDelta(); + } + + d_congruenceManager.addSharedTerm(n); + 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::ppRewrite(TNode atom) { + Debug("arith::preprocess") << "arith::preprocess() : " << atom << endl; + + + if (atom.getKind() == kind::EQUAL && options::arithRewriteEq()) { + Node leq = NodeBuilder<2>(kind::LEQ) << atom[0] << atom[1]; + Node geq = NodeBuilder<2>(kind::GEQ) << atom[0] << atom[1]; + Node rewritten = Rewriter::rewrite(leq.andNode(geq)); + Debug("arith::preprocess") << "arith::preprocess() : returning " + << rewritten << endl; + return rewritten; + } else { + return atom; + } +} + +Theory::PPAssertStatus TheoryArithPrivate::ppAssert(TNode in, SubstitutionMap& outSubstitutions) { + TimerStat::CodeTimer codeTimer(d_statistics.d_simplifyTimer); + Debug("simplify") << "TheoryArithPrivate::solve(" << in << ")" << endl; + + + // Solve equalities + Rational minConstant = 0; + Node minMonomial; + Node minVar; + if (in.getKind() == kind::EQUAL) { + Comparison cmp = Comparison::parseNormalForm(in); + + Polynomial left = cmp.getLeft(); + Polynomial right = cmp.getRight(); + + Monomial m = left.getHead(); + if (m.getVarList().singleton()){ + VarList vl = m.getVarList(); + Node var = vl.getNode(); + if (var.getKind() == kind::VARIABLE){ + // 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 == Rewriter::rewrite(elim)); + + + static const unsigned MAX_SUB_SIZE = 2; + if(right.size() > MAX_SUB_SIZE){ + Debug("simplify") << "TheoryArithPrivate::solve(): did not substitute due to the right hand side containing too many terms: " << minVar << ":" << elim << endl; + Debug("simplify") << right.size() << endl; + // cout << "TheoryArithPrivate::solve(): did not substitute due to the right hand side containing too many terms: " << minVar << ":" << elim << endl; + // cout << right.size() << endl; + }else if(elim.hasSubterm(minVar)){ + Debug("simplify") << "TheoryArithPrivate::solve(): can't substitute due to recursive pattern with sharing: " << minVar << ":" << elim << endl; + // cout << "TheoryArithPrivate::solve(): can't substitute due to recursive pattern with sharing: " << minVar << ":" << elim << endl; + + }else if (!minVar.getType().isInteger() || right.isIntegral()) { + Assert(!elim.hasSubterm(minVar)); + // cannot eliminate integers here unless we know the resulting + // substitution is integral + Debug("simplify") << "TheoryArithPrivate::solve(): substitution " << minVar << " |-> " << elim << endl; + //cout << "TheoryArithPrivate::solve(): substitution " << minVar << " |-> " << elim << endl; + + outSubstitutions.addSubstitution(minVar, elim); + return Theory::PP_ASSERT_STATUS_SOLVED; + } else { + Debug("simplify") << "TheoryArithPrivate::solve(): can't substitute b/c it's integer: " << minVar << ":" << minVar.getType() << " |-> " << elim << ":" << elim.getType() << endl; + //cout << "TheoryArithPrivate::solve(): can't substitute b/c it's integer: " << 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); + //ArithVar varN = requestArithVar(n,false); + //setupInitialValue(varN); + + markSetup(n); + + + if(x.isDivLike()){ + setupDivLike(x); + } + +} + +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(getLogicInfo().isLinear()){ + throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); + } + + setIncomplete(); + d_nlIncomplete = true; + + ++(d_statistics.d_statUserVariables); + requestArithVar(vlNode, false); + //ArithVar av = requestArithVar(vlNode, false); + //setupInitialValue(av); + + markSetup(vlNode); + } + + /* 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::setupDivLike(const Variable& v){ + Assert(v.isDivLike()); + + if(getLogicInfo().isLinear()){ + throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); + } + + Node vnode = v.getNode(); + Assert(isSetup(vnode)); // Otherwise there is some invariant breaking recursion + Polynomial m = Polynomial::parsePolynomial(vnode[0]); + Polynomial n = Polynomial::parsePolynomial(vnode[1]); + + cautiousSetupPolynomial(m); + cautiousSetupPolynomial(n); + + Node lem; + switch(vnode.getKind()){ + case DIVISION: + case INTS_DIVISION: + case INTS_MODULUS: + lem = definingIteForDivLike(vnode); + break; + case DIVISION_TOTAL: + lem = axiomIteForTotalDivision(vnode); + break; + case INTS_DIVISION_TOTAL: + case INTS_MODULUS_TOTAL: + lem = axiomIteForTotalIntDivision(vnode); + break; + default: + /* intentionally blank */ + break; + } + + if(!lem.isNull()){ + Debug("arith::div") << lem << endl; + outputLemma(lem); + } +} + +Node TheoryArithPrivate::definingIteForDivLike(Node divLike){ + Kind k = divLike.getKind(); + Assert(k == DIVISION || k == INTS_DIVISION || k == INTS_MODULUS); + // (for all ((n Real) (d Real)) + // (= + // (DIVISION n d) + // (ite (= d 0) + // (APPLY [div_0_skolem_function] n) + // (DIVISION_TOTAL x y)))) + + Polynomial n = Polynomial::parsePolynomial(divLike[0]); + Polynomial d = Polynomial::parsePolynomial(divLike[1]); + + NodeManager* currNM = NodeManager::currentNM(); + Node dEq0 = currNM->mkNode(EQUAL, d.getNode(), mkRationalNode(0)); + + Kind kTotal = (k == DIVISION) ? DIVISION_TOTAL : + (k == INTS_DIVISION) ? INTS_DIVISION_TOTAL : INTS_MODULUS_TOTAL; + + Node by0Func = (k == DIVISION) ? getRealDivideBy0Func(): + (k == INTS_DIVISION) ? getIntDivideBy0Func() : getIntModulusBy0Func(); + + + Debug("arith::div") << divLike << endl; + Debug("arith::div") << by0Func << endl; + + Node divTotal = currNM->mkNode(kTotal, n.getNode(), d.getNode()); + Node divZero = currNM->mkNode(APPLY_UF, by0Func, n.getNode()); + + Node defining = divLike.eqNode(dEq0.iteNode( divZero, divTotal)); + + return defining; +} + +Node TheoryArithPrivate::axiomIteForTotalDivision(Node div_tot){ + Assert(div_tot.getKind() == DIVISION_TOTAL); + + // Inverse of multiplication axiom: + // (for all ((n Real) (d Real)) + // (ite (= d 0) + // (= (DIVISION_TOTAL n d) 0) + // (= (* d (DIVISION_TOTAL n d)) n))) + + + Polynomial n = Polynomial::parsePolynomial(div_tot[0]); + Polynomial d = Polynomial::parsePolynomial(div_tot[1]); + Polynomial div_tot_p = Polynomial::parsePolynomial(div_tot); + + Comparison invEq = Comparison::mkComparison(EQUAL, n, d * div_tot_p); + Comparison zeroEq = Comparison::mkComparison(EQUAL, div_tot_p, Polynomial::mkZero()); + Node dEq0 = (d.getNode()).eqNode(mkRationalNode(0)); + Node ite = dEq0.iteNode(zeroEq.getNode(), invEq.getNode()); + + return ite; +} + +Node TheoryArithPrivate::axiomIteForTotalIntDivision(Node int_div_like){ + Kind k = int_div_like.getKind(); + Assert(k == INTS_DIVISION_TOTAL || k == INTS_MODULUS_TOTAL); + + // (for all ((m Int) (n Int)) + // (=> (distinct n 0) + // (let ((q (div m n)) (r (mod m n))) + // (and (= m (+ (* n q) r)) + // (<= 0 r (- (abs n) 1)))))) + + // Updated for div 0 functions + // (for all ((m Int) (n Int)) + // (let ((q (div m n)) (r (mod m n))) + // (ite (= n 0) + // (and (= q (div_0_func m)) (= r (mod_0_func m))) + // (and (= m (+ (* n q) r)) + // (<= 0 r (- (abs n) 1))))))) + + Polynomial n = Polynomial::parsePolynomial(int_div_like[0]); + Polynomial d = Polynomial::parsePolynomial(int_div_like[1]); + + NodeManager* currNM = NodeManager::currentNM(); + Node zero = mkRationalNode(0); + + Node q = (k == INTS_DIVISION_TOTAL) ? int_div_like : currNM->mkNode(INTS_DIVISION_TOTAL, n.getNode(), d.getNode()); + Node r = (k == INTS_MODULUS_TOTAL) ? int_div_like : currNM->mkNode(INTS_MODULUS_TOTAL, n.getNode(), d.getNode()); + + Node dEq0 = (d.getNode()).eqNode(zero); + Node qEq0 = q.eqNode(zero); + Node rEq0 = r.eqNode(zero); + + Polynomial rp = Polynomial::parsePolynomial(r); + Polynomial qp = Polynomial::parsePolynomial(q); + + Node abs_d = (n.isConstant()) ? + d.getHead().getConstant().abs().getNode() : mkIntSkolem("abs_$$"); + + Node eq = Comparison::mkComparison(EQUAL, n, d * qp + rp).getNode(); + Node leq0 = currNM->mkNode(LEQ, zero, r); + Node leq1 = currNM->mkNode(LT, r, abs_d); + + Node andE = currNM->mkNode(AND, eq, leq0, leq1); + Node defDivMode = dEq0.iteNode(qEq0.andNode(rEq0), andE); + Node lem = abs_d.getMetaKind () == metakind::VARIABLE ? + defDivMode.andNode(d.makeAbsCondition(Variable(abs_d))) : defDivMode; + + return lem; +} + + +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() == PLUS){ + d_tableauSizeHasBeenModified = true; + + vector variables; + vector coefficients; + asVectors(poly, coefficients, variables); + + ArithVar varSlack = requestArithVar(polyNode, true); + d_tableau.addRow(varSlack, coefficients, variables); + setupBasicValue(varSlack); + d_linEq.trackVariable(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_statSlackVariables); + markSetup(polyNode); + } + + /* Note: + * It is worth documenting that polyNode should only be marked as + * being setup by this function if it has kind PLUS. + * Other kinds will be marked as being setup by lower levels of setup + * specifically setupVariableList. + */ +} + +void TheoryArithPrivate::setupAtom(TNode atom) { + Assert(isRelationOperator(atom.getKind())); + 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) { + Debug("arith::preregister") <<"begin arith::preRegisterTerm("<< n <<")"<< endl; + + if(isRelationOperator(n.getKind())){ + if(!isSetup(n)){ + setupAtom(n); + } + Constraint c = d_constraintDatabase.lookup(n); + Assert(c != NullConstraint); + + Debug("arith::preregister") << "setup constraint" << c << endl; + Assert(!c->canBePropagated()); + c->setPreregistered(); + } + + Debug("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); + d_linEq.maybeRemoveTracking(v); +} + +ArithVar TheoryArithPrivate::requestArithVar(TNode x, bool slack){ + //TODO : The VarList trick is good enough? + Assert(isLeaf(x) || VarList::isMember(x) || x.getKind() == PLUS); + if(getLogicInfo().isLinear() && Variable::isDivMember(x)){ + throw LogicException("Non-linear term was asserted to arithmetic in a linear logic."); + } + Assert(!d_partialModel.hasArithVar(x)); + Assert(x.getType().isReal()); // real or integer + + // ArithVar varX = d_variables.size(); + // d_variables.push_back(Node(x)); + + ArithVar max = d_partialModel.getNumberOfVariables(); + ArithVar varX = d_partialModel.allocate(x, slack); + + bool reclaim = max >= d_partialModel.getNumberOfVariables();; + + if(reclaim){ + // varX = d_pool.back(); + // d_pool.pop_back(); + + // d_partialModel.setAssignment(varX, d_DELTA_ZERO, d_DELTA_ZERO); + }else{ + // varX = d_numberOfVariables; + // ++d_numberOfVariables; + + // d_slackVars.push_back(true); + // d_variableTypes.push_back(ATReal); + + d_dualSimplex.increaseMax(); + + d_tableau.increaseSize(); + d_tableauSizeHasBeenModified = true; + + //d_partialModel.initialize(varX, d_DELTA_ZERO); + } + + // ArithType type; + // if(slack){ + // //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(x); + // type = p.isIntegral() ? ATInteger : ATReal; + // }else{ + // type = nodeToArithType(x); + // } + // d_variableTypes[varX] = type; + // d_slackVars[varX] = slack; + + d_constraintDatabase.addVariable(varX); + + //d_partialModel.setArithVar(x,varX); + + // Debug("integers") << "isInteger[[" << x << "]]: " << x.getType().isInteger() << endl; + + // if(slack){ + // //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(x); + // d_variableTypes.push_back(p.isIntegral() ? ATInteger : ATReal); + // }else{ + // d_variableTypes.push_back(nodeToArithType(x)); + // } + + // d_slackVars.push_back(slack); + + // d_simplex.increaseMax(); + + // d_tableau.increaseSize(); + // d_tableauSizeHasBeenModified = true; + + // d_constraintDatabase.addVariable(varX); + + Debug("arith::arithvar") << x << " |-> " << varX << 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(); + + Debug("rewriter") << "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); + + Debug("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 = Rewriter::rewrite(lemma); + Debug("arith::dio") << "dioCutting found the plane: " << plane.getNode() << endl; + Debug("arith::dio") << "resulting in the cut: " << lemma << endl; + Debug("arith::dio") << "rewritten " << rewrittenLemma << endl; + return rewrittenLemma; + } +} + +Node TheoryArithPrivate::callDioSolver(){ + while(!d_constantIntegerVariables.empty()){ + ArithVar v = d_constantIntegerVariables.front(); + d_constantIntegerVariables.pop(); + + Debug("arith::dio") << v << endl; + + Assert(isInteger(v)); + Assert(d_partialModel.boundsAreEqual(v)); + + + Constraint lb = d_partialModel.getLowerBoundConstraint(v); + Constraint ub = d_partialModel.getUpperBoundConstraint(v); + + Node orig = Node::null(); + if(lb->isEquality()){ + orig = lb->explainForConflict(); + }else if(ub->isEquality()){ + orig = ub->explainForConflict(); + }else { + orig = ConstraintValue::explainConflict(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{ + Debug("dio::push") << v << " " << eq.getNode() << " with reason " << orig << endl; + d_diosolver.pushInputConstraint(eq, orig); + } + } + + return d_diosolver.processEquationsForConflict(); +} + +Constraint TheoryArithPrivate::constraintFromFactQueue(){ + Assert(!done()); + TNode assertion = get(); + + Kind simpleKind = Comparison::comparisonKind(assertion); + Constraint 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 = Rewriter::rewrite(eq); + if(reEq.getKind() == CONST_BOOLEAN){ + if(reEq.getConst() == isDistinct){ + // if is (not true), or false + Assert((reEq.getConst() && isDistinct) || + (!reEq.getConst() && !isDistinct)); + raiseConflict(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){ + Debug("arith::nf") << "getting non-nf assertion " << assertion << " |-> " << reAssertion << endl; + Assert(constraint != NullConstraint); + d_assertionsThatDoNotMatchTheirLiterals.insert(assertion, constraint); + } + } + + // Kind simpleKind = Comparison::comparisonKind(assertion); + // Assert(simpleKind != UNDEFINED_KIND); + // Assert(constraint != NullConstraint || + // simpleKind == EQUAL || + // simpleKind == DISTINCT ); + // if(simpleKind == EQUAL || simpleKind == DISTINCT){ + // Node eq = (simpleKind == DISTINCT) ? assertion[0] : assertion; + + // if(!isSetup(eq)){ + // //The previous code was equivalent to: + // setupAtom(eq); + // constraint = d_constraintDatabase.lookup(assertion); + // } + // } + Assert(constraint != NullConstraint); + + if(constraint->negationHasProof()){ + Constraint negation = constraint->getNegation(); + if(negation->isSelfExplaining()){ + if(Debug.isOn("whytheoryenginewhy")){ + debugPrintFacts(); + } + } + Debug("arith::eq") << constraint << endl; + Debug("arith::eq") << negation << endl; + + NodeBuilder<> nb(kind::AND); + nb << assertion; + negation->explainForConflict(nb); + Node conflict = nb; + Debug("arith::eq") << "conflict" << conflict << endl; + raiseConflict(conflict); + return NullConstraint; + } + Assert(!constraint->negationHasProof()); + + if(constraint->assertedToTheTheory()){ + //Do nothing + return NullConstraint; + }else{ + Debug("arith::constraint") << "arith constraint " << constraint << std::endl; + constraint->setAssertedToTheTheory(assertion); + + if(!constraint->hasProof()){ + Debug("arith::constraint") << "marking as constraint as self explaining " << endl; + constraint->selfExplaining(); + }else{ + Debug("arith::constraint") << "already has proof: " << constraint->explainForConflict() << endl; + } + + return constraint; + } +} + +bool TheoryArithPrivate::assertionCases(Constraint constraint){ + Assert(constraint->hasProof()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + + switch(constraint->getType()){ + case UpperBound: + if(isInteger(x_i) && constraint->isStrictUpperBound()){ + Constraint floorConstraint = constraint->getFloor(); + if(!floorConstraint->isTrue()){ + if(floorConstraint->negationHasProof()){ + Node conf = ConstraintValue::explainConflict(constraint, floorConstraint->getNegation()); + raiseConflict(conf); + return true; + }else{ + floorConstraint->impliedBy(constraint); + // Do not need to add to d_learnedBounds + } + } + return AssertUpper(floorConstraint); + }else{ + return AssertUpper(constraint); + } + case LowerBound: + if(isInteger(x_i) && constraint->isStrictLowerBound()){ + Constraint ceilingConstraint = constraint->getCeiling(); + if(!ceilingConstraint->isTrue()){ + if(ceilingConstraint->negationHasProof()){ + Node conf = ConstraintValue::explainConflict(constraint, ceilingConstraint->getNegation()); + raiseConflict(conf); + return true; + } + ceilingConstraint->impliedBy(constraint); + // Do not need to add to learnedBounds + } + return AssertLower(ceilingConstraint); + }else{ + return AssertLower(constraint); + } + case Equality: + return AssertEquality(constraint); + case Disequality: + return AssertDisequality(constraint); + default: + Unreachable(); + return false; + } +} + +/** + * Looks for the next integer variable without an integer assignment in a round robin fashion. + * Changes the value of d_nextIntegerCheckVar. + * + * If this returns false, d_nextIntegerCheckVar does not have an integer assignment. + * If this returns true, all integer variables have an integer assignment. + */ +bool TheoryArithPrivate::hasIntegerModel(){ + //if(d_variables.size() > 0){ + ArithVar numVars = d_partialModel.getNumberOfVariables(); + if(numVars > 0){ + const ArithVar rrEnd = d_nextIntegerCheckVar; + do { + //Do not include slack variables + if(isInteger(d_nextIntegerCheckVar) && !isSlackVariable(d_nextIntegerCheckVar)) { // integer + const DeltaRational& d = d_partialModel.getAssignment(d_nextIntegerCheckVar); + if(!d.isIntegral()){ + return false; + } + } + } while((d_nextIntegerCheckVar = (1 + d_nextIntegerCheckVar == numVars ? 0 : 1 + d_nextIntegerCheckVar)) != rrEnd); + } + return true; +} + +/** Outputs conflicts to the output channel. */ +void TheoryArithPrivate::outputConflicts(){ + Assert(!d_conflicts.empty()); + for(size_t i = 0, i_end = d_conflicts.size(); i < i_end; ++i){ + Node conflict = d_conflicts[i]; + Debug("arith::conflict") << "d_conflicts[" << i << "] " << conflict << endl; + (d_containing.d_out)->conflict(conflict); + } +} + +void TheoryArithPrivate::branchVector(const std::vector& lemmas){ + //output the lemmas + for(vector::const_iterator i = lemmas.begin(); i != lemmas.end(); ++i){ + ArithVar v = *i; + Assert(!d_cutInContext.contains(v)); + d_cutInContext.insert(v); + d_cutCount = d_cutCount + 1; + Node lem = branchIntegerVariable(v); + outputLemma(lem); + ++(d_statistics.d_externalBranchAndBounds); + } +} + +bool TheoryArithPrivate::solveRealRelaxation(Theory::Effort effortLevel){ + Assert(d_qflraStatus != Result::SAT); + + d_partialModel.stopQueueingAtBoundQueue(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processAtBoundQueue(utcb); + d_linEq.startTrackingBoundCounts(); + + bool noPivotLimit = Theory::fullEffort(effortLevel) || + !options::restrictedPivots(); + + bool emmittedConflictOrSplit = false; + + SimplexDecisionProcedure& simplex = + options::useFC() ? (SimplexDecisionProcedure&)d_fcSimplex : + (options::useSOI() ? (SimplexDecisionProcedure&)d_soiSimplex : + (SimplexDecisionProcedure&)d_dualSimplex); + + bool useFancyFinal = options::fancyFinal() && ApproximateSimplex::enabled(); + + if(!useFancyFinal){ + d_qflraStatus = simplex.findModel(noPivotLimit); + }else{ + // Fancy final tries the following strategy + // At final check, try the preferred simplex solver with a pivot cap + // If that failed, swap the the other simplex solver + // If that failed, check if there are integer variables to cut + // If that failed, do a simplex without a pivot limit + + int16_t oldCap = options::arithStandardCheckVarOrderPivots(); + + static const int32_t pass2Limit = 10; + static const int32_t relaxationLimit = 10000; + static const int32_t mipLimit = 200000; + + d_qflraStatus = simplex.findModel(false); + if(d_qflraStatus == Result::SAT_UNKNOWN || + (d_qflraStatus == Result::SAT && !hasIntegerModel())){ + + ApproximateSimplex* approxSolver = ApproximateSimplex::mkApproximateSimplexSolver(d_partialModel); + approxSolver->setPivotLimit(relaxationLimit); + + ApproximateSimplex::ApproxResult relaxRes, mipRes; + ApproximateSimplex::Solution relaxSolution, mipSolution; + relaxRes = approxSolver->solveRelaxation(); + switch(relaxRes){ + case ApproximateSimplex::ApproxSat: + { + relaxSolution = approxSolver->extractRelaxation(); + approxSolver->setPivotLimit(mipLimit); + mipRes = approxSolver->solveMIP(); + d_errorSet.reduceToSignals(); + if(mipRes == ApproximateSimplex::ApproxSat){ + mipSolution = approxSolver->extractMIP(); + ApproximateSimplex::applySolution(d_linEq, mipSolution); + }else{ + ApproximateSimplex::applySolution(d_linEq, relaxSolution); + // if(d_qflraStatus != UNSAT){ + // d_likelyIntegerUnsat = true; + // } + } + options::arithStandardCheckVarOrderPivots.set(pass2Limit); + d_qflraStatus = simplex.findModel(false); + } + break; + case ApproximateSimplex::ApproxUnsat: + { + ApproximateSimplex::Solution sol = approxSolver->extractRelaxation(); + d_errorSet.reduceToSignals(); + ApproximateSimplex::applySolution(d_linEq, sol); + options::arithStandardCheckVarOrderPivots.set(100); + + d_qflraStatus = simplex.findModel(false); + } + break; + default: + break; + } + delete approxSolver; + } + + if(d_qflraStatus == Result::SAT_UNKNOWN){ + vector toCut = cutAllBounded(); + if(toCut.size() > 0){ + branchVector(toCut); + emmittedConflictOrSplit = true; + }else{ + d_qflraStatus = simplex.findModel(noPivotLimit); + } + } + options::arithStandardCheckVarOrderPivots.set(oldCap); + } + + // TODO Save zeroes with no conflicts + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingAtBoundQueue(); + + return emmittedConflictOrSplit; +} + +void TheoryArithPrivate::check(Theory::Effort effortLevel){ + Assert(d_currentPropagationList.empty()); + Debug("effortlevel") << "TheoryArithPrivate::check " << effortLevel << std::endl; + Debug("arith") << "TheoryArithPrivate::check begun " << effortLevel << std::endl; + + if(Debug.isOn("arith::consistency")){ + Assert(unenqueuedVariablesAreConsistent()); + } + + bool newFacts = !done(); + //If previous == SAT, then reverts on conflicts are safe + //Otherwise, they are not and must be committed. + Result::Sat previous = d_qflraStatus; + if(newFacts){ + d_qflraStatus = Result::SAT_UNKNOWN; + d_hasDoneWorkSinceCut = true; + } + + while(!done()){ + Constraint curr = constraintFromFactQueue(); + if(curr != NullConstraint){ + bool res CVC4_UNUSED = assertionCases(curr); + Assert(!res || inConflict()); + } + if(inConflict()){ break; } + } + if(!inConflict()){ + while(!d_learnedBounds.empty()){ + // we may attempt some constraints twice. this is okay! + Constraint curr = d_learnedBounds.front(); + d_learnedBounds.pop(); + Debug("arith::learned") << curr << endl; + + bool res CVC4_UNUSED = assertionCases(curr); + Assert(!res || inConflict()); + + if(inConflict()){ break; } + } + } + + if(inConflict()){ + d_qflraStatus = Result::UNSAT; + if(options::revertArithModels() && previous == Result::SAT){ + ++d_statistics.d_revertsOnConflicts; + Debug("arith::bt") << "clearing here " << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + revertOutOfConflict(); + d_errorSet.clear(); + }else{ + ++d_statistics.d_commitsOnConflicts; + Debug("arith::bt") << "committing here " << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + revertOutOfConflict(); + } + outputConflicts(); + return; + } + + + if(Debug.isOn("arith::print_assertions")) { + debugPrintAssertions(); + } + + bool emmittedConflictOrSplit = false; + Assert(d_conflicts.empty()); + + bool useSimplex = d_qflraStatus != Result::SAT; + if(useSimplex){ + emmittedConflictOrSplit = solveRealRelaxation(effortLevel); + } + + switch(d_qflraStatus){ + case Result::SAT: + if(newFacts){ + ++d_statistics.d_nontrivialSatChecks; + } + + Debug("arith::bt") << "committing sap inConflit" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + d_unknownsInARow = 0; + if(Debug.isOn("arith::consistency")){ + Assert(entireStateIsConsistent("sat comit")); + } + if(useSimplex && options::collectPivots()){ + if(options::useFC()){ + d_statistics.d_satPivots << d_fcSimplex.getPivots(); + }else{ + d_statistics.d_satPivots << d_dualSimplex.getPivots(); + } + } + break; + case Result::SAT_UNKNOWN: + ++d_unknownsInARow; + ++(d_statistics.d_unknownChecks); + Assert(!Theory::fullEffort(effortLevel)); + Debug("arith::bt") << "committing unknown" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + d_statistics.d_maxUnknownsInARow.maxAssign(d_unknownsInARow); + + if(useSimplex && options::collectPivots()){ + if(options::useFC()){ + d_statistics.d_unknownPivots << d_fcSimplex.getPivots(); + }else{ + d_statistics.d_unknownPivots << d_dualSimplex.getPivots(); + } + } + break; + case Result::UNSAT: + d_unknownsInARow = 0; + if(false && previous == Result::SAT){ + ++d_statistics.d_revertsOnConflicts; + Debug("arith::bt") << "clearing on conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + revertOutOfConflict(); + d_errorSet.clear(); + }else{ + ++d_statistics.d_commitsOnConflicts; + + Debug("arith::bt") << "committing on conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + revertOutOfConflict(); + + if(Debug.isOn("arith::consistency::comitonconflict")){ + entireStateIsConsistent("commit on conflict"); + } + } + outputConflicts(); + emmittedConflictOrSplit = true; + + if(useSimplex && options::collectPivots()){ + if(options::useFC()){ + d_statistics.d_unsatPivots << d_fcSimplex.getPivots(); + }else{ + d_statistics.d_unsatPivots << d_dualSimplex.getPivots(); + } + } + break; + default: + Unimplemented(); + } + d_statistics.d_avgUnknownsInARow.addEntry(d_unknownsInARow); + + // This should be fine if sat or unknown + if(!emmittedConflictOrSplit && + (options::arithPropagationMode() == UNATE_PROP || + options::arithPropagationMode() == BOTH_PROP)){ + TimerStat::CodeTimer codeTimer(d_statistics.d_newPropTime); + Assert(d_qflraStatus != Result::UNSAT); + + while(!d_currentPropagationList.empty() && !inConflict()){ + Constraint 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: + { + Constraint prev = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropLowerBound(curr, prev); + break; + } + case UpperBound: + { + Constraint prev = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropUpperBound(curr, prev); + break; + } + case Equality: + { + Constraint prevLB = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + Constraint prevUB = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropEquality(curr, prevLB, prevUB); + break; + } + default: + Unhandled(curr->getType()); + } + } + + if(inConflict()){ + Debug("arith::unate") << "unate conflict" << endl; + revertOutOfConflict(); + d_qflraStatus = Result::UNSAT; + outputConflicts(); + emmittedConflictOrSplit = true; + Debug("arith::bt") << "committing on unate conflict" << " " << newFacts << " " << previous << " " << d_qflraStatus << endl; + + } + }else{ + TimerStat::CodeTimer codeTimer(d_statistics.d_newPropTime); + d_currentPropagationList.clear(); + } + Assert( d_currentPropagationList.empty()); + + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ + ++d_fullCheckCounter; + } + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ + emmittedConflictOrSplit = splitDisequalities(); + } + emmittedConflictOrSplit = false; + + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel) && !hasIntegerModel()){ + Node possibleConflict = Node::null(); + if(!emmittedConflictOrSplit && options::arithDioSolver()){ + possibleConflict = callDioSolver(); + if(possibleConflict != Node::null()){ + revertOutOfConflict(); + Debug("arith::conflict") << "dio conflict " << possibleConflict << endl; + //cout << "dio conflict " << possibleConflict << endl; + raiseConflict(possibleConflict); + outputConflicts(); + emmittedConflictOrSplit = true; + } + } + + if(!emmittedConflictOrSplit && d_hasDoneWorkSinceCut && options::arithDioSolver()){ + Node possibleLemma = dioCutting(); + if(!possibleLemma.isNull()){ + Debug("arith") << "dio cut " << possibleLemma << endl; + //cout << "dio cut " << possibleLemma << endl; + emmittedConflictOrSplit = true; + d_hasDoneWorkSinceCut = false; + d_cutCount = d_cutCount + 1; + outputLemma(possibleLemma); + } + } + + if(!emmittedConflictOrSplit) { + Node possibleLemma = roundRobinBranch(); + if(!possibleLemma.isNull()){ + ++(d_statistics.d_externalBranchAndBounds); + d_cutCount = d_cutCount + 1; + emmittedConflictOrSplit = true; + outputLemma(possibleLemma); + } + } + + if(options::maxCutsInContext() <= d_cutCount){ + if(d_diosolver.hasMoreDecompositionLemmas()){ + while(d_diosolver.hasMoreDecompositionLemmas()){ + Node decompositionLemma = d_diosolver.nextDecompositionLemma(); + Debug("arith") << "dio decomposition lemma " << decompositionLemma << endl; + outputLemma(decompositionLemma); + } + }else{ + outputRestart(); + } + } + }//if !emmittedConflictOrSplit && fullEffort(effortLevel) && !hasIntegerModel() + if(Theory::fullEffort(effortLevel) && d_nlIncomplete){ + // TODO this is total paranoia + setIncomplete(); + } + + if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + if(Debug.isOn("arith::print_model")) { debugPrintModel(); } + Debug("arith") << "TheoryArithPrivate::check end" << std::endl; +} + +Node 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)); + Assert(!d.isIntegral()); + TNode var = d_partialModel.asNode(x); + Integer floor_d = d.floor(); + + //Node eq = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::EQUAL, var, mkRationalNode(floor_d+1))); + //Node diseq = eq.notNode(); + + Node ub = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::LEQ, var, mkRationalNode(floor_d))); + Node lb = ub.notNode(); + + + //Node lem = NodeManager::currentNM()->mkNode(kind::OR, eq, diseq); + Node lem = NodeManager::currentNM()->mkNode(kind::OR, ub, lb); + Trace("integers") << "integers: branch & bound: " << lem << endl; + if(isSatLiteral(lem[0])) { + Debug("integers") << " " << lem[0] << " == " << getSatValue(lem[0]) << endl; + } else { + Debug("integers") << " " << lem[0] << " is not assigned a SAT literal" << endl; + } + if(isSatLiteral(lem[1])) { + Debug("integers") << " " << lem[1] << " == " << getSatValue(lem[1]) << endl; + } else { + Debug("integers") << " " << lem[1] << " is not assigned a SAT literal" << endl; + } + return lem; +} + +std::vector TheoryArithPrivate::cutAllBounded() const{ + vector lemmas; + ArithVar max = d_partialModel.getNumberOfVariables(); + + if(options::doCutAllBounded() && max > 0){ + for(ArithVar iter = 0; iter != max; ++iter){ + //Do not include slack variables + const DeltaRational& d = d_partialModel.getAssignment(iter); + if(isInteger(iter) && !isSlackVariable(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. */ +Node TheoryArithPrivate::roundRobinBranch(){ + if(hasIntegerModel()){ + return Node::null(); + }else{ + ArithVar v = d_nextIntegerCheckVar; + + Assert(isInteger(v)); + Assert(!isSlackVariable(v)); + return branchIntegerVariable(v); + } +} + +bool TheoryArithPrivate::splitDisequalities(){ + bool splitSomething = false; + + vector save; + + while(!d_diseqQueue.empty()){ + Constraint front = d_diseqQueue.front(); + d_diseqQueue.pop(); + + if(front->isSplit()){ + Debug("eq") << "split already" << endl; + }else{ + Debug("eq") << "not split already" << endl; + + ArithVar lhsVar = front->getVariable(); + + const DeltaRational& lhsValue = d_partialModel.getAssignment(lhsVar); + const DeltaRational& rhsValue = front->getValue(); + if(lhsValue == rhsValue){ + Debug("arith::lemma") << "Splitting on " << front << endl; + Debug("arith::lemma") << "LHS value = " << lhsValue << endl; + Debug("arith::lemma") << "RHS value = " << rhsValue << endl; + Node lemma = front->split(); + ++(d_statistics.d_statDisequalitySplits); + + Debug("arith::lemma") << "Now " << Rewriter::rewrite(lemma) << endl; + outputLemma(lemma); + splitSomething = true; + }else if(d_partialModel.strictlyLessThanLowerBound(lhsVar, rhsValue)){ + Debug("eq") << "can drop as less than lb" << front << endl; + }else if(d_partialModel.strictlyGreaterThanUpperBound(lhsVar, rhsValue)){ + Debug("eq") << "can drop as greater than ub" << front << endl; + }else{ + Debug("eq") << "save" << front << ": " <::const_iterator it = d_diseqQueue.begin(); + context::CDQueue::const_iterator it_end = d_diseqQueue.end(); + for(; it != it_end; ++ it) { + Debug("arith::print_assertions") << *it << endl; + } +} + +void TheoryArithPrivate::debugPrintModel(){ + Debug("arith::print_model") << "Model:" << endl; + for (var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar i = *vi; + if(d_partialModel.hasNode(i)){ + Debug("arith::print_model") << d_partialModel.asNode(i) << " : " << + d_partialModel.getAssignment(i); + if(d_tableau.isBasic(i)) + Debug("arith::print_model") << " (basic)"; + Debug("arith::print_model") << endl; + } + } +} + + + +Node TheoryArithPrivate::explain(TNode n) { + + Debug("arith::explain") << "explain @" << getSatContext()->getLevel() << ": " << n << endl; + + Constraint c = d_constraintDatabase.lookup(n); + if(c != NullConstraint){ + Assert(!c->isSelfExplaining()); + Node exp = c->explainForPropagation(); + Debug("arith::explain") << "constraint explanation" << n << ":" << exp << endl; + return exp; + }else if(d_assertionsThatDoNotMatchTheirLiterals.find(n) != d_assertionsThatDoNotMatchTheirLiterals.end()){ + c = d_assertionsThatDoNotMatchTheirLiterals[n]; + if(!c->isSelfExplaining()){ + Node exp = c->explainForPropagation(); + Debug("arith::explain") << "assertions explanation" << n << ":" << exp << endl; + return exp; + }else{ + Debug("arith::explain") << "this is a strange mismatch" << n << endl; + Assert(d_congruenceManager.canExplain(n)); + Debug("arith::explain") << "this is a strange mismatch" << n << endl; + return d_congruenceManager.explain(n); + } + }else{ + Assert(d_congruenceManager.canExplain(n)); + Debug("arith::explain") << "dm explanation" << n << endl; + return d_congruenceManager.explain(n); + } +} + + +void TheoryArithPrivate::propagate(Theory::Effort e) { + // This uses model values for safety. Disable for now. + if(d_qflraStatus == Result::SAT && + (options::arithPropagationMode() == BOUND_INFERENCE_PROP || + options::arithPropagationMode() == BOTH_PROP) + && hasAnyUpdates()){ + propagateCandidates(); + }else{ + clearUpdates(); + } + + while(d_constraintDatabase.hasMorePropagations()){ + Constraint c = d_constraintDatabase.nextPropagation(); + Debug("arith::prop") << "next prop" << getSatContext()->getLevel() << ": " << c << endl; + + if(c->negationHasProof()){ + Debug("arith::prop") << "negation has proof " << c->getNegation() << endl + << c->getNegation()->explainForConflict() << 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(); + Debug("arith::prop") << "propagating @" << getSatContext()->getLevel() << " " << literal << endl; + + outputPropagate(literal); + }else{ + Debug("arith::prop") << "already asserted to the theory " << c->getLiteral() << endl; + } + } + + 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 = Rewriter::rewrite(toProp); + + Constraint constraint = d_constraintDatabase.lookup(normalized); + if(constraint == NullConstraint){ + Debug("arith::prop") << "propagating on non-constraint? " << toProp << endl; + + outputPropagate(toProp); + }else if(constraint->negationHasProof()){ + Node exp = d_congruenceManager.explain(toProp); + Node notNormalized = normalized.getKind() == NOT ? + normalized[0] : normalized.notNode(); + Node lp = flattenAnd(exp.andNode(notNormalized)); + Debug("arith::prop") << "propagate conflict" << lp << endl; + raiseConflict(lp); + outputConflicts(); + return; + }else{ + Debug("arith::prop") << "propagating still?" << toProp << endl; + outputPropagate(toProp); + } + } +} + +DeltaRational TheoryArithPrivate::getDeltaValue(TNode n) const throw (DeltaRationalException, ModelException) { + AlwaysAssert(d_qflraStatus != Result::SAT_UNKNOWN); + Debug("arith::value") << n << std::endl; + + switch(n.getKind()) { + + case kind::CONST_RATIONAL: + return n.getConst(); + + case kind::PLUS: { // 2+ args + DeltaRational value(0); + for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { + value = value + getDeltaValue(*i); + } + return value; + } + + case kind::MULT: { // 2+ args + DeltaRational value(1); + unsigned variableParts = 0; + for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { + TNode curr = *i; + value = value * getDeltaValue(curr); + if(!curr.isConst()){ + ++variableParts; + } + } + // TODO: This is a bit of a weak check + if(isSetup(n)){ + ArithVar var = d_partialModel.asArithVar(n); + const DeltaRational& assign = d_partialModel.getAssignment(var); + if(assign != value){ + throw ModelException(n, "Model disagrees on non-linear term."); + } + } + return value; + } + case kind::MINUS:{ // 2 args + return getDeltaValue(n[0]) - getDeltaValue(n[1]); + } + + case kind::UMINUS:{ // 1 arg + return (- getDeltaValue(n[0])); + } + + case kind::DIVISION:{ // 2 args + DeltaRational res = getDeltaValue(n[0]) / getDeltaValue(n[1]); + if(isSetup(n)){ + ArithVar var = d_partialModel.asArithVar(n); + if(d_partialModel.getAssignment(var) != res){ + throw ModelException(n, "Model disagrees on non-linear term."); + } + } + return res; + } + case kind::DIVISION_TOTAL: + case kind::INTS_DIVISION_TOTAL: + case kind::INTS_MODULUS_TOTAL: { // 2 args + DeltaRational denom = getDeltaValue(n[1]); + if(denom.isZero()){ + return DeltaRational(0,0); + }else{ + DeltaRational numer = getDeltaValue(n[0]); + DeltaRational res; + if(n.getKind() == kind::DIVISION_TOTAL){ + res = numer / denom; + }else if(n.getKind() == kind::INTS_DIVISION_TOTAL){ + res = Rational(numer.euclidianDivideQuotient(denom)); + }else{ + Assert(n.getKind() == kind::INTS_MODULUS_TOTAL); + res = Rational(numer.euclidianDivideRemainder(denom)); + } + if(isSetup(n)){ + ArithVar var = d_partialModel.asArithVar(n); + if(d_partialModel.getAssignment(var) != res){ + throw ModelException(n, "Model disagrees on non-linear term."); + } + } + return res; + } + } + + default: + if(isSetup(n)){ + ArithVar var = d_partialModel.asArithVar(n); + return d_partialModel.getAssignment(var); + }else{ + throw ModelException(n, "Expected a setup node."); + } + } +} + +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){ + Constraint 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::collectModelInfo( TheoryModel* m, bool fullModel ){ + AlwaysAssert(d_qflraStatus == Result::SAT); + //AlwaysAssert(!d_nlIncomplete, "Arithmetic solver cannot currently produce models for input with nonlinear arithmetic constraints"); + + if(Debug.isOn("arith::collectModelInfo")){ + debugPrintFacts(); + } + + Debug("arith::collectModelInfo") << "collectModelInfo() begin " << endl; + + + // Delta lasts at least the duration of the function call + const Rational& delta = d_partialModel.getDelta(); + std::hash_set shared = d_containing.currentlySharedTerms(); + + // TODO: + // This is not very good for user push/pop.... + // Revisit when implementing push/pop + for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar v = *vi; + + if(!isSlackVariable(v)){ + Node term = d_partialModel.asNode(v); + + if(theoryOf(term) == THEORY_ARITH || shared.find(term) != shared.end()){ + const DeltaRational& mod = d_partialModel.getAssignment(v); + Rational qmodel = mod.substituteDelta(delta); + + Node qNode = mkRationalNode(qmodel); + Debug("arith::collectModelInfo") << "m->assertEquality(" << term << ", " << qmodel << ", true)" << endl; + + m->assertEquality(term, qNode, true); + }else{ + Debug("arith::collectModelInfo") << "Skipping m->assertEquality(" << term << ", true)" << endl; + + } + } + } + + // Iterate over equivalence classes in LinearEqualityModule + // const eq::EqualityEngine& ee = d_congruenceManager.getEqualityEngine(); + // m->assertEqualityEngine(&ee); + + Debug("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(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + + ++d_restartsCounter; +#warning "removing restart" + // return; + + // uint32_t currSize = d_tableau.size(); + // uint32_t copySize = d_smallTableauCopy.size(); + + // Debug("arith::reset") << "resetting" << d_restartsCounter << endl; + // Debug("arith::reset") << "curr " << currSize << " copy " << copySize << endl; + // Debug("arith::reset") << "tableauSizeHasBeenModified " << d_tableauSizeHasBeenModified << endl; + + // if(d_tableauSizeHasBeenModified){ + // Debug("arith::reset") << "row has been added must copy " << d_restartsCounter << endl; + // d_smallTableauCopy = d_tableau; + // d_tableauSizeHasBeenModified = false; + // }else if( d_restartsCounter >= RESET_START){ + // if(copySize >= currSize * 1.1 ){ + // Debug("arith::reset") << "size has shrunk " << d_restartsCounter << endl; + // ++d_statistics.d_smallerSetToCurr; + // d_smallTableauCopy = d_tableau; + // }else if(d_tableauResetDensity * copySize <= currSize){ + // d_errorSet.popAllSignals(); + // if(safeToReset()){ + // Debug("arith::reset") << "resetting " << d_restartsCounter << endl; + // ++d_statistics.d_currSetToSmaller; + // d_tableau = d_smallTableauCopy; + // }else{ + // Debug("arith::reset") << "not safe to reset at the moment " << d_restartsCounter << endl; + // } + // } + // } + // Assert(unenqueuedVariablesAreConsistent()); +} + +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() << 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() << endl; + result = false; + } else if(Debug.isOn("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() << endl; + } + } + } + return result; +} + +void TheoryArithPrivate::presolve(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_presolveTime); + + d_statistics.d_initialTableauSize.setData(d_tableau.size()); + + if(Debug.isOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + + static CVC4_THREADLOCAL(unsigned) callCount = 0; + if(Debug.isOn("arith::presolve")) { + Debug("arith::presolve") << "TheoryArithPrivate::presolve #" << callCount << endl; + callCount = callCount + 1; + } + + vector lemmas; + switch(options::arithUnateLemmaMode()){ + case NO_PRESOLVE_LEMMAS: + break; + case INEQUALITY_PRESOLVE_LEMMAS: + d_constraintDatabase.outputUnateInequalityLemmas(lemmas); + break; + case EQUALITY_PRESOLVE_LEMMAS: + d_constraintDatabase.outputUnateEqualityLemmas(lemmas); + break; + case ALL_PRESOLVE_LEMMAS: + d_constraintDatabase.outputUnateInequalityLemmas(lemmas); + d_constraintDatabase.outputUnateEqualityLemmas(lemmas); + break; + default: + Unhandled(options::arithUnateLemmaMode()); + } + + vector::const_iterator i = lemmas.begin(), i_end = lemmas.end(); + for(; i != i_end; ++i){ + Node lem = *i; + Debug("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 + + // Constraint c = d_constraintDatabase.lookup(bestImplied); + // Assert(c != NullConstraint); + + bool assertedToTheTheory = bestImplied->assertedToTheTheory(); + bool canBePropagated = bestImplied->canBePropagated(); + bool hasProof = bestImplied->hasProof(); + + Debug("arith::prop") << "arith::prop" << basic + << " " << assertedToTheTheory + << " " << canBePropagated + << " " << hasProof + << endl; + + if(bestImplied->negationHasProof()){ + Warning() << "the negation of " << bestImplied << " : " << endl + << "has proof " << bestImplied->getNegation() << endl + << bestImplied->getNegation()->explainForConflict() << endl; + } + + if(!assertedToTheTheory && canBePropagated && !hasProof ){ + if(upperBound){ + Assert(bestImplied != d_partialModel.getUpperBoundConstraint(basic)); + d_linEq.propagateNonbasicsUpperBound(basic, bestImplied); + }else{ + Assert(bestImplied != d_partialModel.getLowerBoundConstraint(basic)); + d_linEq.propagateNonbasicsLowerBound(basic, bestImplied); + } + // I think this can be skipped if canBePropagated is true + //d_learnedBounds.push(bestImplied); + return true; + } + } + } + return false; +} + +void TheoryArithPrivate::propagateCandidate(ArithVar basic){ + bool success = false; + if(d_partialModel.strictlyAboveLowerBound(basic) && d_linEq.hasLowerBounds(basic)){ + success |= propagateCandidateLowerBound(basic); + } + if(d_partialModel.strictlyBelowUpperBound(basic) && d_linEq.hasUpperBounds(basic)){ + success |= propagateCandidateUpperBound(basic); + } + if(success){ + ++d_statistics.d_boundPropagations; + } +} + +void TheoryArithPrivate::propagateCandidates(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); + + 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::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::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); + } +} + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/theory_arith_private.h b/src/theory/arith/theory_arith_private.h new file mode 100644 index 000000000..7b37a813f --- /dev/null +++ b/src/theory/arith/theory_arith_private.h @@ -0,0 +1,605 @@ +/********************* */ +/*! \file theory_arith.cpp + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): kshitij, ajreynol, mdeters, dejan + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief [[ Add one-line brief description here ]] + ** + ** [[ Add lengthier description here ]] + ** \todo document this file + **/ + +#pragma once + +#include "theory/arith/theory_arith_private_forward.h" + +#include "expr/node.h" +#include "expr/kind.h" +#include "expr/metakind.h" +#include "expr/node_builder.h" + +#include "context/context.h" +#include "context/cdlist.h" +#include "context/cdhashset.h" +#include "context/cdinsert_hashmap.h" +#include "context/cdqueue.h" + +#include "theory/valuation.h" +#include "theory/rewriter.h" + +#include "util/rational.h" +#include "util/integer.h" +#include "util/boolean_simplification.h" +#include "util/dense_map.h" +#include "util/statistics_registry.h" +#include "util/result.h" + +#include "smt/logic_exception.h" + + + +#include "theory/arith/arithvar.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/matrix.h" +#include "theory/arith/arith_rewriter.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/linear_equality.h" +#include "theory/arith/arith_static_learner.h" +#include "theory/arith/dio_solver.h" +#include "theory/arith/congruence_manager.h" + +#include "theory/arith/simplex.h" +#include "theory/arith/dual_simplex.h" +#include "theory/arith/fc_simplex.h" +#include "theory/arith/soi_simplex.h" +#include "theory/arith/pure_update_simplex.h" + +#include "theory/arith/constraint.h" + +#include "theory/arith/arith_utilities.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/matrix.h" + +#include "theory/arith/arith_rewriter.h" +#include "theory/arith/constraint.h" +#include "theory/arith/theory_arith.h" +#include "theory/arith/normal_form.h" +#include "theory/model.h" + +#include "theory/arith/options.h" + +#include + +#include +#include +#include + +namespace CVC4 { +namespace theory { +namespace quantifiers { + class InstStrategySimplex; +} +namespace arith { + +/** + * Implementation of QF_LRA. + * Based upon: + * http://research.microsoft.com/en-us/um/people/leonardo/cav06.pdf + */ +class TheoryArithPrivate { +private: + friend class quantifiers::InstStrategySimplex; + + static const uint32_t RESET_START = 2; + + TheoryArith& d_containing; + + bool d_nlIncomplete; + // TODO A better would be: + //context::CDO d_nlIncomplete; + + BoundCountingVector d_boundTracking; + + /** + * The constraint database associated with the theory. + * This must be declared before ArithPartialModel. + */ + ConstraintDatabase d_constraintDatabase; + + enum Result::Sat 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; + + + /** + * 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: + /** + * The map between arith variables to nodes. + */ + //ArithVarNodeMap d_arithvarNodeMap; + + 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 setupDivLike(const Variable& x); + + 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); + //return d_variableTypes[x] >= ATInteger; + } + + /** This is the set of variables initially introduced as slack variables. */ + //std::vector d_slackVars; + + /** Returns true if the variable was initially introduced as a slack variable. */ + inline bool isSlackVariable(ArithVar x) const{ + return d_partialModel.isSlack(x); + //return d_slackVars[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(); + Node 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; + + + /** + * 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 const uint32_t s_TABLEAU_RESET_INCREMENT = 5; + + + /** This is only used by simplex at the moment. */ + context::CDList d_conflicts; +public: + inline void raiseConflict(Node n){ d_conflicts.push_back(n); } + +private: + + /** Returns true iff a conflict has been raised. */ + inline bool inConflict() const { + return !d_conflicts.empty(); + } + + /** + * Outputs the contents of d_conflicts onto d_out. + * Must be inConflict(). + */ + 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; + + /** This implements the Simplex decision procedure. */ + DualSimplexDecisionProcedure d_dualSimplex; + PureUpdateSimplexDecisionProcedure d_pureUpdate; + FCSimplexDecisionProcedure d_fcSimplex; + SumOfInfeasibilitiesSPD d_soiSimplex; + + bool solveRealRelaxation(Theory::Effort effortLevel); + + class ModelException : public Exception { + public: + ModelException(TNode n, const char* msg) throw (); + virtual ~ModelException() throw (); + }; + + /** Internal model value for the node */ + DeltaRational getDeltaValue(TNode n) const throw (DeltaRationalException, ModelException); + + /** Uninterpretted function symbol for use when interpreting + * division by zero. + */ + Node d_realDivideBy0Func; + Node d_intDivideBy0Func; + Node d_intModulusBy0Func; + Node getRealDivideBy0Func(); + Node getIntDivideBy0Func(); + Node getIntModulusBy0Func(); + + Node definingIteForDivLike(Node divLike); + Node axiomIteForTotalDivision(Node div_tot); + Node axiomIteForTotalIntDivision(Node int_div_like); + + + +public: + TheoryArithPrivate(TheoryArith& containing, context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo, QuantifiersEngine* qe); + ~TheoryArithPrivate(); + + /** + * Does non-context dependent setup for a node connected to a theory. + */ + void preRegisterTerm(TNode n); + + void setMasterEqualityEngine(eq::EqualityEngine* eq); + + void check(Theory::Effort e); + void propagate(Theory::Effort e); + Node explain(TNode n); + + + Rational deltaValueForTotalOrder() const; + + void collectModelInfo( TheoryModel* m, bool fullModel ); + + void shutdown(){ } + + void presolve(); + void notifyRestart(); + Theory::PPAssertStatus ppAssert(TNode in, SubstitutionMap& outSubstitutions); + Node ppRewrite(TNode atom); + void ppStaticLearn(TNode in, NodeBuilder<>& learned); + + std::string identify() const { return std::string("TheoryArith"); } + + EqualityStatus getEqualityStatus(TNode a, TNode b); + + void addSharedTerm(TNode n); + +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. + * + * If this returns false, d_nextIntegerCheckVar does not have an integer assignment. + * If this returns true, all integer variables have an integer assignment. + */ + bool hasIntegerModel(); + + /** + * Issues branches for non-slack integer variables with non-integer assignments. + * Returns a cut for a lemma. + * If there is an integer model, this returns Node::null(). + */ + Node roundRobinBranch(); + +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. + */ + ArithVar requestArithVar(TNode x, bool slack); + +private: + /** Initial (not context dependent) sets up for a variable.*/ + void setupBasicValue(ArithVar x); + + /** Initial (not context dependent) sets up for a new slack variable.*/ + void setupSlack(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(Constraint constraint); + bool AssertUpper(Constraint constraint); + bool AssertEquality(Constraint constraint); + bool AssertDisequality(Constraint 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; + + bool hasAnyUpdates() { return !d_updatedBounds.empty(); } + void clearUpdates(); + + void revertOutOfConflict(); + + 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. + */ + Constraint constraintFromFactQueue(); + bool assertionCases(Constraint 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(); + /** Debugging only routine. Prints the model. */ + void debugPrintModel(); + + inline LogicInfo getLogicInfo() const { return d_containing.getLogicInfo(); } + inline bool done() const { return d_containing.done(); } + inline TNode get() { return d_containing.get(); } + inline bool isLeaf(TNode x) const { return d_containing.isLeaf(x); } + inline TheoryId theoryOf(TNode x) const { return d_containing.theoryOf(x); } + inline void debugPrintFacts() const { d_containing.debugPrintFacts(); } + inline context::Context* getSatContext() const { return d_containing.getSatContext(); } + inline void setIncomplete() { + (d_containing.d_out)->setIncomplete(); + d_nlIncomplete = true; + } + inline void outputLemma(TNode lem) { (d_containing.d_out)->lemma(lem); } + inline void outputPropagate(TNode lit) { (d_containing.d_out)->propagate(lit); } + inline void outputRestart() { (d_containing.d_out)->demandRestart(); } + + 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); + } + + + /** Counts the number of fullCheck calls to arithmetic. */ + uint32_t d_fullCheckCounter; + std::vector cutAllBounded() const; + Node branchIntegerVariable(ArithVar x) const; + void branchVector(const std::vector& lemmas); + + context::CDO d_cutCount; + context::CDHashSet > d_cutInContext; + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_statAssertUpperConflicts, d_statAssertLowerConflicts; + + IntStat d_statUserVariables, d_statSlackVariables; + 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; + + + HistogramStat d_satPivots; + HistogramStat d_unsatPivots; + HistogramStat d_unknownPivots; + + Statistics(); + ~Statistics(); + }; + + Statistics d_statistics; + + +};/* class TheoryArithPrivate */ + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/arith/theory_arith_private_forward.h b/src/theory/arith/theory_arith_private_forward.h new file mode 100644 index 000000000..0dc7cdd5b --- /dev/null +++ b/src/theory/arith/theory_arith_private_forward.h @@ -0,0 +1,14 @@ + +#include "cvc4_private.h" + +#pragma once + +namespace CVC4 { +namespace theory { +namespace arith { + +class TheoryArithPrivate; + +}/* CVC4::theory::arith namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ diff --git a/src/theory/quantifiers/inst_strategy_cbqi.cpp b/src/theory/quantifiers/inst_strategy_cbqi.cpp index 20eb7373b..dbdf95613 100644 --- a/src/theory/quantifiers/inst_strategy_cbqi.cpp +++ b/src/theory/quantifiers/inst_strategy_cbqi.cpp @@ -14,6 +14,8 @@ #include "theory/quantifiers/inst_strategy_cbqi.h" #include "theory/arith/theory_arith.h" +#include "theory/arith/partial_model.h" +#include "theory/arith/theory_arith_private.h" #include "theory/theory_engine.h" #include "theory/quantifiers/options.h" #include "theory/quantifiers/term_database.h" @@ -46,11 +48,11 @@ void InstStrategySimplex::processResetInstantiationRound( Theory::Effort effort d_tableaux.clear(); d_ceTableaux.clear(); //search for instantiation rows in simplex tableaux - ArithVarNodeMap& avnm = d_th->d_arithvarNodeMap; - ArithVarNodeMap::var_iterator vi, vend; + ArithVariables& avnm = d_th->d_internal->d_partialModel; + ArithVariables::var_iterator vi, vend; for(vi = avnm.var_begin(), vend = avnm.var_end(); vi != vend; ++vi ){ ArithVar x = *vi; - if( d_th->d_partialModel.hasEitherBound( x ) ){ + if( d_th->d_internal->d_partialModel.hasEitherBound( x ) ){ Node n = avnm.asNode(x); Node f; NodeBuilder<> t(kind::PLUS); @@ -168,23 +170,23 @@ void InstStrategySimplex::addTermToRow( ArithVar x, Node n, Node& f, NodeBuilder } void InstStrategySimplex::debugPrint( const char* c ){ - const ArithVarNodeMap& avnm = d_th->d_arithvarNodeMap; - ArithVarNodeMap::var_iterator vi, vend; + ArithVariables& avnm = d_th->d_internal->d_partialModel; + ArithVariables::var_iterator vi, vend; for(vi = avnm.var_begin(), vend = avnm.var_end(); vi != vend; ++vi ){ ArithVar x = *vi; Node n = avnm.asNode(x); //if( ((TheoryArith*)getTheory())->d_partialModel.hasEitherBound( x ) ){ Debug(c) << x << " : " << n << ", bounds = "; - if( d_th->d_partialModel.hasLowerBound( x ) ){ - Debug(c) << d_th->d_partialModel.getLowerBound( x ); + if( d_th->d_internal->d_partialModel.hasLowerBound( x ) ){ + Debug(c) << d_th->d_internal->d_partialModel.getLowerBound( x ); }else{ Debug(c) << "-infty"; } Debug(c) << " <= "; - Debug(c) << d_th->d_partialModel.getAssignment( x ); + Debug(c) << d_th->d_internal->d_partialModel.getAssignment( x ); Debug(c) << " <= "; - if( d_th->d_partialModel.hasUpperBound( x ) ){ - Debug(c) << d_th->d_partialModel.getUpperBound( x ); + if( d_th->d_internal->d_partialModel.hasUpperBound( x ) ){ + Debug(c) << d_th->d_internal->d_partialModel.getUpperBound( x ); }else{ Debug(c) << "+infty"; } @@ -273,8 +275,8 @@ bool InstStrategySimplex::doInstantiation2( Node f, Node term, ArithVar x, InstM } Node InstStrategySimplex::getTableauxValue( Node n, bool minus_delta ){ - if( d_th->d_arithvarNodeMap.hasArithVar(n) ){ - ArithVar v = d_th->d_arithvarNodeMap.asArithVar( n ); + if( d_th->d_internal->d_partialModel.hasArithVar(n) ){ + ArithVar v = d_th->d_internal->d_partialModel.asArithVar( n ); return getTableauxValue( v, minus_delta ); }else{ return NodeManager::currentNM()->mkConst( Rational(0) ); @@ -282,8 +284,8 @@ Node InstStrategySimplex::getTableauxValue( Node n, bool minus_delta ){ } Node InstStrategySimplex::getTableauxValue( ArithVar v, bool minus_delta ){ - const Rational& delta = d_th->d_partialModel.getDelta(); - DeltaRational drv = d_th->d_partialModel.getAssignment( v ); + const Rational& delta = d_th->d_internal->d_partialModel.getDelta(); + DeltaRational drv = d_th->d_internal->d_partialModel.getAssignment( v ); Rational qmodel = drv.substituteDelta( minus_delta ? -delta : delta ); return mkRationalNode(qmodel); } diff --git a/src/theory/quantifiers/inst_strategy_cbqi.h b/src/theory/quantifiers/inst_strategy_cbqi.h index 5528d70ea..a45318489 100644 --- a/src/theory/quantifiers/inst_strategy_cbqi.h +++ b/src/theory/quantifiers/inst_strategy_cbqi.h @@ -19,7 +19,7 @@ #define __CVC4__INST_STRATEGT_CBQI_H #include "theory/quantifiers/instantiation_engine.h" -#include "theory/arith/arithvar_node_map.h" +#include "theory/arith/arithvar.h" #include "util/statistics_registry.h" @@ -107,4 +107,4 @@ public: } } -#endif \ No newline at end of file +#endif diff --git a/src/util/Makefile.am b/src/util/Makefile.am index f95382cb1..4f93f5a61 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -39,6 +39,7 @@ libutil_la_SOURCES = \ datatype.h \ datatype.cpp \ tuple.h \ + maybe.h \ record.h \ record.cpp \ matcher.h \ diff --git a/src/util/configuration.cpp b/src/util/configuration.cpp index ba141cc58..f03c5e959 100644 --- a/src/util/configuration.cpp +++ b/src/util/configuration.cpp @@ -121,6 +121,10 @@ bool Configuration::isBuiltWithCln() { return IS_CLN_BUILD; } +bool Configuration::isBuiltWithGlpk() { + return IS_GLPK_BUILD; +} + bool Configuration::isBuiltWithCudd() { return false; } diff --git a/src/util/configuration.h b/src/util/configuration.h index a63a0d6f3..7e543c54e 100644 --- a/src/util/configuration.h +++ b/src/util/configuration.h @@ -87,6 +87,8 @@ public: static bool isBuiltWithCln(); + static bool isBuiltWithGlpk(); + static bool isBuiltWithCudd(); static bool isBuiltWithTlsSupport(); diff --git a/src/util/configuration_private.h b/src/util/configuration_private.h index 272766ff4..3b3115887 100644 --- a/src/util/configuration_private.h +++ b/src/util/configuration_private.h @@ -101,6 +101,12 @@ namespace CVC4 { # define IS_CLN_BUILD false #endif /* CVC4_CLN_IMP */ +#if CVC4_USE_GLPK +# define IS_GLPK_BUILD true +#else /* CVC4_USE_GLPK */ +# define IS_GLPK_BUILD false +#endif /* CVC4_USE_GLPK */ + #ifdef TLS # define USING_TLS true #else /* TLS */ @@ -124,13 +130,20 @@ Copyright (C) 2009, 2010, 2011, 2012, 2013\n\ This CVC4 library uses CLN as its multi-precision arithmetic library.\n\n\ CVC4 is open-source and is covered by the BSD license (modified).\n\ However, CLN, the Class Library for Numbers, is covered by the GPLv3,\n\ -and so this \"combined\" work, CVC4+CLN, is covered by the GPLv3 as well\n\ +and so this \"combined\" work, CVC4+CLN, is covered by the GPLv3 as well.\n\ Please consult the CVC4 documentation for instructions about building\n\ without CLN if you want to license CVC4 under the (modified) BSD license.\n\n\ +" : ( IS_GLPK_BUILD ? "\ +This CVC4 library uses GLPK in its arithmetic solver.\n\n\ +CVC4 is open-source and is covered by the BSD license (modified).\n\ +However, GLPK, the GNU Linear Programming Kit, is covered by the GPLv3,\n\ +and so this \"combined\" work, CVC4+GLPK, is covered by the GPLv3 as well.\n\ +Please consult the CVC4 documentation for instructions about building\n\ +without GLPK if you want to license CVC4 under the (modified) BSD license.\n\n\ " : \ "This CVC4 library uses GMP as its multi-precision arithmetic library.\n\n\ CVC4 is open-source and is covered by the BSD license (modified).\n\n\ -" ) + "\ +" ) ) + "\ THIS SOFTWARE PROVIDED AS-IS, WITHOUT ANY WARRANTIES. USE AT YOUR OWN RISK.\n\n\ See the file COPYING (distributed with the source code, and with all binaries)\n\ for the full CVC4 copyright, licensing, and (lack of) warranty information.\n" ) diff --git a/src/util/dense_map.h b/src/util/dense_map.h index b8acf1556..8f8b63668 100644 --- a/src/util/dense_map.h +++ b/src/util/dense_map.h @@ -29,6 +29,7 @@ #include #include #include "util/index.h" +#include "util/cvc4_assert.h" namespace CVC4 { @@ -287,15 +288,36 @@ public: bool isMember(Element x) const{ return d_map.isKey(x); } - void add(Element x){ + void add(Element x, CountType c = 1u){ + Assert(c > 0); if(d_map.isKey(x)){ - d_map.set(x, d_map.get(x)+1); + d_map.set(x, d_map.get(x)+c); }else{ - d_map.set(x,1); + d_map.set(x,c); } } - void remove(Element x){ return d_map.remove(x); } + void setCount(Element x, CountType c){ + d_map.set(x, c); + } + + void removeAll(Element x){ return d_map.remove(x); } + + void removeOne(Element x){ + CountType c = count(x); + switch(c){ + case 0: break; // do nothing + case 1: removeAll(x); break; // remove + default: d_map.set(x, c-1); break; // decrease + } + } + + void removeOneOfEverything(){ + BackingMap::KeyList keys(d_map.begin(), d_map.end()); + for(BackingMap::const_iterator i=keys.begin(), i_end = keys.end(); i != i_end; ++i){ + removeOne(*i); + } + } CountType count(Element x) const { if(d_map.isKey(x)){ diff --git a/src/util/maybe.h b/src/util/maybe.h new file mode 100644 index 000000000..795eac759 --- /dev/null +++ b/src/util/maybe.h @@ -0,0 +1,84 @@ +/********************* */ +/*! \file maybe.h + ** \verbatim + ** Original author: taking + ** Major contributors: none + ** Minor contributors (to current version): mdeters + ** This file is part of the CVC4 prototype. + ** Copyright (c) 2009-2012 New York University and The University of Iowa + ** See the file COPYING in the top-level source directory for licensing + ** information.\endverbatim + ** + ** \brief This provides a templated Maybe construct. + ** + ** This class provides a templated Maybe construct. + ** This follows the rough pattern of the Maybe monad in haskell. + ** A Maybe is an algebraic type that is either Nothing | Just T + ** + ** T must support T() and operator=. + ** + ** This has a couple of uses: + ** - There is no reasonable value or particularly clean way to represent + ** Nothing using a value of T + ** - High level of assurance that a value is not used before it is set. + **/ +#include "cvc4_private.h" + +#pragma once + +#include +#include "util/exception.h" + +namespace CVC4 { + +template +class Maybe { +private: + bool d_just; + T d_value; + +public: + Maybe() : d_just(false), d_value(){} + Maybe(const T& val): d_just(true), d_value(val){} + + Maybe& operator=(const T& v){ + d_just = true; + d_value = v; + return *this; + } + + inline bool nothing() const { return !d_just; } + inline bool just() const { return d_just; } + + void clear() { + if(just()){ + d_just = false; + d_value = T(); + } + } + + T& value() { + Assert(just(), "Maybe::value() requires the maybe to be set."); + return d_value; + } + const T& constValue() const { + Assert(just(), "Maybe::constValue() requires the maybe to be set."); + return d_value; + } + + operator const T&() const { return constValue(); } +}; + +template +inline std::ostream& operator<<(std::ostream& out, const Maybe& m){ + out << "{"; + if(m.nothing()){ + out << "Nothing"; + }else{ + out << m.constValue(); + } + out << "}"; + return out; +} + +}/* CVC4 namespace */ diff --git a/src/util/rational_cln_imp.h b/src/util/rational_cln_imp.h index 476ddd544..1e27fa859 100644 --- a/src/util/rational_cln_imp.h +++ b/src/util/rational_cln_imp.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "util/exception.h" #include "util/integer.h" @@ -139,16 +141,16 @@ public: * Constructs a canonical Rational from a numerator and denominator. */ Rational(signed int n, signed int d) : d_value((signed long int)n) { - d_value /= d; + d_value /= cln::cl_I(d); } Rational(unsigned int n, unsigned int d) : d_value((unsigned long int)n) { - d_value /= d; + d_value /= cln::cl_I(d); } Rational(signed long int n, signed long int d) : d_value(n) { - d_value /= d; + d_value /= cln::cl_I(d); } Rational(unsigned long int n, unsigned long int d) : d_value(n) { - d_value /= d; + d_value /= cln::cl_I(d); } #ifdef CVC4_NEED_INT64_T_OVERLOADS @@ -186,6 +188,14 @@ public: return Integer(cln::denominator(d_value)); } + /** Return an exact rational for a double d. */ + static Rational fromDouble(double d){ + cln::cl_DF fromD = d; + Rational q; + q.d_value = cln::rationalize(fromD); + return q; + } + /** * Get a double representation of this Rational, which is * approximate: truncation may occur, overflow may result in @@ -302,6 +312,11 @@ public: return (*this); } + Rational& operator-=(const Rational& y){ + d_value -= y.d_value; + return (*this); + } + Rational& operator*=(const Rational& y){ d_value *= y.d_value; return (*this); @@ -330,6 +345,10 @@ public: return equal_hashcode(d_value); } + uint32_t complexity() const { + return getNumerator().length() + getDenominator().length(); + } + };/* class Rational */ struct RationalHashFunction { diff --git a/src/util/rational_gmp_imp.h b/src/util/rational_gmp_imp.h index 0916b523a..02ccc273c 100644 --- a/src/util/rational_gmp_imp.h +++ b/src/util/rational_gmp_imp.h @@ -156,7 +156,6 @@ public: } ~Rational() {} - /** * Returns the value of numerator of the Rational. * Note that this makes a deep copy of the numerator. @@ -173,6 +172,13 @@ public: return Integer(d_value.get_den()); } + /** Return an exact rational for a double d. */ + static Rational fromDouble(double d){ + Rational q; + mpq_set_d(q.d_value.get_mpq_t(), d); + return q; + } + /** * Get a double representation of this Rational, which is * approximate: truncation may occur, overflow may result in @@ -280,6 +286,10 @@ public: d_value += y.d_value; return (*this); } + Rational& operator-=(const Rational& y){ + d_value -= y.d_value; + return (*this); + } Rational& operator*=(const Rational& y){ d_value *= y.d_value; @@ -311,6 +321,11 @@ public: return numeratorHash xor denominatorHash; } + uint32_t complexity() const { + uint32_t numLen = getNumerator().length(); + uint32_t denLen = getDenominator().length(); + return numLen + denLen; + } };/* class Rational */ struct RationalHashFunction { diff --git a/src/util/statistics_registry.h b/src/util/statistics_registry.h index e0bc81d91..3bec559d5 100644 --- a/src/util/statistics_registry.h +++ b/src/util/statistics_registry.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -537,6 +538,47 @@ public: };/* class ListStat */ +template +class HistogramStat : public Stat { +private: + typedef std::map Histogram; + Histogram d_hist; +public: + + /** Construct a histogram of a stream of entries. */ + HistogramStat(const std::string& name) : Stat(name) {} + ~HistogramStat() {} + + void flushInformation(std::ostream& out) const{ + if(__CVC4_USE_STATISTICS) { + typename Histogram::const_iterator i = d_hist.begin(); + typename Histogram::const_iterator end = d_hist.end(); + out << "["; + while(i != end){ + const T& key = (*i).first; + unsigned int count = (*i).second; + out << "("<