diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 000000000..b3a17f301 --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,35 @@ +name: Android Build + +on: + push: + branches: [ master ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + android-abi: [arm64-v8a, armeabi-v7a, x86, x86_64] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure CMake and build + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.android-abi }} -DCMAKE_ANDROID_NDK=$ANDROID_NDK_HOME -DZ3_BUILD_JAVA_BINDINGS=TRUE -G "Unix Makefiles" -DJAVA_AWT_LIBRARY=NotNeeded -DJAVA_JVM_LIBRARY=NotNeeded -DJAVA_INCLUDE_PATH2=NotNeeded -DJAVA_AWT_INCLUDE_PATH=NotNeeded ../ + make -j $(nproc) + tar -cvf z3-build-${{ matrix.android-abi }}.tar *.jar *.so + + - name: Archive production artifacts + uses: actions/upload-artifact@v2 + with: + name: android-build-${{ matrix.android-abi }} + path: build/z3-build-${{ matrix.android-abi }}.tar diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 000000000..0a2ebc3d2 --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,42 @@ +name: WASM Build + +on: + push: + branches: [ master ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Import emscripten + uses: mymindstorm/setup-emsdk@v9 + + - name: Configure CMake and build + run: | + mkdir build + cd build + + emcmake cmake \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DZ3_BUILD_LIBZ3_SHARED=OFF \ + -DZ3_ENABLE_EXAMPLE_TARGETS=OFF \ + -DZ3_BUILD_TEST_EXECUTABLES=OFF \ + -DZ3_BUILD_EXECUTABLE=OFF \ + -DZ3_SINGLE_THREADED=ON \ + -DCMAKE_CXX_FLAGS="-s DISABLE_EXCEPTION_CATCHING=0" \ + ..; + make + tar -cvf z3-build-wasm.tar *.a + + - name: Archive production artifacts + uses: actions/upload-artifact@v2 + with: + name: z3-build-wasm + path: build/z3-build-wasm.tar diff --git a/README.md b/README.md index 1eda3db92..5c5af803b 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ See the [release notes](RELEASE_NOTES) for notes on various stable releases of Z ## Build status -| Azure Pipelines | Code Coverage | Open Bugs | -| --------------- | --------------|-----------| -| [![Build Status](https://dev.azure.com/Z3Public/Z3/_apis/build/status/Z3Prover.z3?branchName=master)](https://dev.azure.com/Z3Public/Z3/_build/latest?definitionId=1&branchName=master) | [![CodeCoverage](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml) | [![Open Issues](https://github.com/Z3Prover/z3/actions/workflows/wip.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/wip.yml) | +| Azure Pipelines | Code Coverage | Open Bugs | Android Build | WASM Build | +| --------------- | --------------|-----------|---------------|------------| +| [![Build Status](https://dev.azure.com/Z3Public/Z3/_apis/build/status/Z3Prover.z3?branchName=master)](https://dev.azure.com/Z3Public/Z3/_build/latest?definitionId=1&branchName=master) | [![CodeCoverage](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml) | [![Open Issues](https://github.com/Z3Prover/z3/actions/workflows/wip.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/wip.yml) |[![Android Build](https://github.com/Z3Prover/z3/actions/workflows/android-build.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/android-build.yml) | [![WASM Build](https://github.com/Z3Prover/z3/actions/workflows/wasm.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/wasm.yml) | [1]: #building-z3-on-windows-using-visual-studio-command-prompt [2]: #building-z3-using-make-and-gccclang diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ed2a7d0c3..ea894a93a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,29 +43,61 @@ jobs: - ${{if eq(variables['runRegressions'], 'True')}}: - template: scripts/test-regressions.yml -- job: "Ubuntu18Python" - displayName: "Ubuntu 18 with ocaml" +- job: "Ubuntu20OCaml" + displayName: "Ubuntu 20 with OCaml" pool: - vmImage: "Ubuntu-18.04" + vmImage: "Ubuntu-20.04" steps: - script: sudo apt-get install ocaml opam libgmp-dev - script: opam init -y - script: eval `opam config env`; opam install zarith ocamlfind -y - - script: python scripts/mk_make.py --ml --staticlib + - script: eval `opam config env`; python scripts/mk_make.py --ml - script: | set -e cd build - eval `opam config env` + eval `opam config env` make -j3 make -j3 examples make -j3 test-z3 - ./ml_example cd .. + - script: eval `opam config env`; ocamlfind install z3 build/api/ml/* -dll build/libz3.* - template: scripts/test-z3.yml - template: scripts/test-regressions.yml - template: scripts/generate-doc.yml +- job: "Ubuntu20OCamlStatic" + displayName: "Ubuntu 20 with OCaml on z3-static" + pool: + vmImage: "Ubuntu-20.04" + steps: + - script: sudo apt-get install ocaml opam libgmp-dev + - script: opam init -y + - script: eval `opam config env`; opam install zarith ocamlfind -y + - script: eval `opam config env`; python scripts/mk_make.py --ml --staticlib + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 examples + make -j3 test-z3 + cd .. + - script: eval `opam config env`; ocamlfind install z3-static build/api/ml/* build/libz3-static.a + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 _ex_ml_example_post_install + ./ml_example_static.byte + ./ml_example_static_custom.byte + ./ml_example_static + cd .. + - template: scripts/test-z3.yml + - template: scripts/test-regressions.yml + - template: scripts/generate-doc.yml + - job: "LinuxMSan" displayName: "Ubuntu build - cmake" condition: eq(0,1) @@ -100,7 +132,7 @@ jobs: # - template: scripts/test-java-cmake.yml # - template: scripts/test-regressions.yml -- job: "Ubuntu16CMake" +- job: "UbuntuCMake" displayName: "Ubuntu build - cmake" pool: vmImage: "Ubuntu-latest" @@ -252,3 +284,36 @@ jobs: # - template: scripts/test-examples-cmake.yml - template: scripts/test-regressions.yml # - template: scripts/test-java-cmake.yml + + +- job: "MacOSOCaml" + displayName: "MacOS build with OCaml" + pool: + vmImage: "macOS-latest" + steps: + - script: brew install opam + - script: opam init -y + - script: eval `opam config env`; opam install zarith ocamlfind -y + - script: eval `opam config env`; python scripts/mk_make.py --ml + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 examples + make -j3 test-z3 + cd .. + - script: eval `opam config env`; ocamlfind install z3 build/api/ml/* -dll build/libz3.* + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 _ex_ml_example_post_install + ./ml_example_shared.byte + ./ml_example_shared_custom.byte + ./ml_example_shared + cd .. +# Skip as dead-slow in debug mode: +# - template: scripts/test-z3.yml + - template: scripts/test-regressions.yml diff --git a/cmake/compiler_warnings.cmake b/cmake/compiler_warnings.cmake index 4a5819fe0..339c2e6f5 100644 --- a/cmake/compiler_warnings.cmake +++ b/cmake/compiler_warnings.cmake @@ -39,6 +39,27 @@ set(CLANG_WARNINGS_AS_ERRORS "-Werror=delete-non-virtual-dtor" # https://clang.llvm.org/docs/DiagnosticsReference.html#woverloaded-virtual "-Werror=overloaded-virtual" + # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to + # track down memory errors + "-Werror=non-virtual-dtor" + # warn if a null dereference is detected + "-Werror=null-dereference" + # warn for potential performance problem casts + # "-Werror=cast-align" + # warn if float is implicit promoted to double + # "-Werror=double-promotion" + "-Werror=no-unreachable-code-return" + # warn the user if a variable declaration shadows one from a parent context + # "-Werror=shadow" + # warn for c-style casts + # "-Werror=old-style-cast" + # warn on sign conversions + # "-Werror=sign-conversion" + # warn on type conversions that may lose data + # "-Werror=conversion" + # warn on anything being unused + # "-Werror=unused" ) ################################################################################ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c407da365..dcc21f255 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -106,6 +106,24 @@ ExternalProject_Add(z3_tptp5 ) set_target_properties(z3_tptp5 PROPERTIES EXCLUDE_FROM_ALL TRUE) +################################################################################ +# Build example user-propagator project using libz3's C++ API as an external project +################################################################################ +ExternalProject_Add(userPropagator + DEPENDS libz3 + # Configure step + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/userPropagator" + CMAKE_ARGS + "-DZ3_DIR=${PROJECT_BINARY_DIR}" + "${EXTERNAL_PROJECT_CMAKE_BUILD_TYPE_ARG}" + # Build step + ${EXTERNAL_PROJECT_BUILD_ALWAYS_ARG} + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/userPropagator_build_dir" + # Install Step + INSTALL_COMMAND "${CMAKE_COMMAND}" -E echo "" # Dummy command + ) +set_target_properties(userPropagator PROPERTIES EXCLUDE_FROM_ALL TRUE) + ################################################################################ # Build Python examples ################################################################################ @@ -118,4 +136,4 @@ endif() ################################################################################ if (Z3_BUILD_DOTNET_BINDINGS) add_subdirectory(dotnet) -endif() \ No newline at end of file +endif() diff --git a/examples/c++/CMakeLists.txt b/examples/c++/CMakeLists.txt index 6223ea7c7..0b152f6ac 100644 --- a/examples/c++/CMakeLists.txt +++ b/examples/c++/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(cpp_example example.cpp) target_include_directories(cpp_example PRIVATE ${Z3_CXX_INCLUDE_DIRS}) target_link_libraries(cpp_example PRIVATE ${Z3_LIBRARIES}) +target_compile_options(cpp_example PRIVATE ${Z3_COMPONENT_CXX_FLAGS}) + if (CMAKE_SYSTEM_NAME MATCHES "[Ww]indows") # On Windows we need to copy the Z3 libraries # into the same directory as the executable diff --git a/examples/c++/example.cpp b/examples/c++/example.cpp index 9ae621dbe..15a37be31 100644 --- a/examples/c++/example.cpp +++ b/examples/c++/example.cpp @@ -304,7 +304,7 @@ void error_example() { // The next call fails because x is a Boolean. expr n = x + 1; } - catch (exception ex) { + catch (exception & ex) { std::cout << "failed: " << ex << "\n"; } @@ -312,7 +312,7 @@ void error_example() { try { expr arg = to_expr(c, Z3_get_app_arg(c, x, 0)); } - catch (exception ex) { + catch (exception & ex) { std::cout << "failed: " << ex << "\n"; } } @@ -1249,10 +1249,14 @@ void recfun_example() { static void string_values() { context c; + std::cout << "string_values\n"; expr s = c.string_val("abc\n\n\0\0", 7); std::cout << s << "\n"; std::string s1 = s.get_string(); std::cout << s1 << "\n"; + std::u32string buffer = s.get_u32string(); + for (unsigned ch : buffer) + std::cout << "char: " << ch << "\n"; } expr MakeStringConstant(context* context, std::string value) { diff --git a/examples/c/test_capi.c b/examples/c/test_capi.c index 4ebe7c314..a9c472f37 100644 --- a/examples/c/test_capi.c +++ b/examples/c/test_capi.c @@ -22,11 +22,11 @@ Copyright (c) 2015 Microsoft Corporation /** \defgroup capi_ex C API examples */ -/*@{*/ +/**@{*/ /** @name Auxiliary Functions */ -/*@{*/ +/**@{*/ /** \brief exit gracefully in case of error. @@ -694,12 +694,12 @@ void display_version() Z3_get_version(&major, &minor, &build, &revision); printf("Z3 %d.%d.%d.%d\n", major, minor, build, revision); } -/*@}*/ +/**@}*/ /** @name Examples */ -/*@{*/ +/**@{*/ /** \brief "Hello world" example: create a Z3 logical context, and delete it. */ @@ -2947,8 +2947,8 @@ void mk_model_example() { Z3_del_context(ctx); } -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ diff --git a/examples/maxsat/maxsat.c b/examples/maxsat/maxsat.c index a0c0cfeec..3e8bf76a5 100644 --- a/examples/maxsat/maxsat.c +++ b/examples/maxsat/maxsat.c @@ -15,7 +15,7 @@ Copyright (c) 2015 Microsoft Corporation /** \defgroup maxsat_ex MaxSAT/MaxSMT examples */ -/*@{*/ +/**@{*/ /** \brief Exit gracefully in case of error. @@ -638,5 +638,5 @@ int main(int argc, char * argv[]) { return 0; } -/*@}*/ +/**@}*/ diff --git a/examples/python/efsmt.py b/examples/python/efsmt.py new file mode 100644 index 000000000..53c83c02e --- /dev/null +++ b/examples/python/efsmt.py @@ -0,0 +1,47 @@ +from z3 import * +from z3.z3util import get_vars + +''' +Modified from the example in pysmt +https://github.com/pysmt/pysmt/blob/97088bf3b0d64137c3099ef79a4e153b10ccfda7/examples/efsmt.py +''' +def efsmt(y, phi, maxloops=None): + """Solving exists x. forall y. phi(x, y)""" + vars = get_vars(phi) + x = [item for item in vars if item not in y] + esolver = Solver() + fsolver = Solver() + esolver.add(BoolVal(True)) + loops = 0 + while maxloops is None or loops <= maxloops: + loops += 1 + eres = esolver.check() + if eres == unsat: + return unsat + else: + emodel = esolver.model() + tau = [emodel.eval(var, True) for var in x] + sub_phi = phi + for i in range(len(x)): + sub_phi = simplify(substitute(sub_phi, (x[i], tau[i]))) + fsolver.add(Not(sub_phi)) + if fsolver.check() == sat: + fmodel = fsolver.model() + sigma = [fmodel.eval(v, True) for v in y] + sub_phi = phi + for j in range(len(y)): + sub_phi = simplify(substitute(sub_phi, (y[j], sigma[j]))) + esolver.add(sub_phi) + else: + return sat + return unknown + + +def test(): + x, y, z = Reals("x y z") + fmla = Implies(And(y > 0, y < 10), y - 2 * x < 7) + fmlb = And(y > 3, x == 1) + print(efsmt([y], fmla)) + print(efsmt([y], fmlb)) + +test() diff --git a/examples/userPropagator/CMakeLists.txt b/examples/userPropagator/CMakeLists.txt new file mode 100644 index 000000000..9ed916d46 --- /dev/null +++ b/examples/userPropagator/CMakeLists.txt @@ -0,0 +1,45 @@ +################################################################################ +# Example C++ project +################################################################################ +project(Z3_USER_PROPAGATOR_EXAMPLE CXX) +cmake_minimum_required(VERSION 3.4) +find_package(Z3 + REQUIRED + CONFIG + # `NO_DEFAULT_PATH` is set so that -DZ3_DIR has to be passed to find Z3. + # This should prevent us from accidentally picking up an installed + # copy of Z3. This is here to benefit Z3's build system when building + # this project. When making your own project you probably shouldn't + # use this option. + NO_DEFAULT_PATH +) + +################################################################################ +# Z3 C++ API bindings require C++11 +################################################################################ +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +message(STATUS "Z3_FOUND: ${Z3_FOUND}") +message(STATUS "Found Z3 ${Z3_VERSION_STRING}") +message(STATUS "Z3_DIR: ${Z3_DIR}") + +add_executable(user_propagator_example example.cpp) +target_include_directories(user_propagator_example PRIVATE ${Z3_CXX_INCLUDE_DIRS}) +target_link_libraries(user_propagator_example PRIVATE ${Z3_LIBRARIES}) + +if (CMAKE_SYSTEM_NAME MATCHES "[Ww]indows") + # On Windows we need to copy the Z3 libraries + # into the same directory as the executable + # so that they can be found. + foreach (z3_lib ${Z3_LIBRARIES}) + message(STATUS "Adding copy rule for ${z3_lib}") + add_custom_command(TARGET user_propagator_example + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) + endforeach() +endif() diff --git a/examples/userPropagator/README b/examples/userPropagator/README new file mode 100644 index 000000000..780a334ef --- /dev/null +++ b/examples/userPropagator/README @@ -0,0 +1,10 @@ +Small example using the user-propagator. +To build the example execute + make examples +in the build directory. + +This command will create the executable user_propagator_example. +On Windows, you can just execute it. +On macOS and Linux, you must install z3 first using + sudo make install +OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS) with the build directory. You need that to be able to find the Z3 shared library. diff --git a/examples/userPropagator/example.cpp b/examples/userPropagator/example.cpp new file mode 100644 index 000000000..b66e3bb0e --- /dev/null +++ b/examples/userPropagator/example.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z3++.h" + +/** + * The program solves the n-queens problem (number of solutions) with 4 different approaches + * 1) Bit-Vector constraints + Default solver + Blocking Clauses + * 2) Bit-Vector constraints + Simple solver + Blocking Clauses + * 3) Bit-Vector constraints + Simple solver + Adding contradictions in the propagator + * 4) Constraints only implicit via the propagator + Simple solver + Adding contradictions in the propagator + * + * Runs 1 + 2 are done for comparison with 3 + 4 + */ + +using namespace std::chrono; +using std::to_string; + +#define QUEEN +#define REPETITIONS 5 + +#define SIZE(x) std::extent::value + +#ifdef LOG +#define WriteEmptyLine std::cout << std::endl +#define WriteLine(x) std::cout << (x) << std::endl +#define Write(x) std::cout << x +#else +#define WriteEmptyLine +#define WriteLine(x) +#define Write(x) +#endif + +typedef std::vector model; + +struct model_hash_function { + std::size_t operator()(const model &m) const { + size_t hash = 0; + for (unsigned i = 0; i < m.size(); i++) { + hash *= m.size(); + hash += m[i]; + } + return hash; + } +}; + +class user_propagator : public z3::user_propagator_base { + +protected: + + unsigned board; + std::unordered_map& id_mapping; + model currentModel; + std::unordered_set modelSet; + std::vector fixedValues; + std::stack fixedCnt; + + int solutionId = 1; + +public: + + int getModelCount() const { + return solutionId - 1; + } + + void final() final { + this->conflict((unsigned) fixedValues.size(), fixedValues.data()); + if (modelSet.find(currentModel) != modelSet.end()) { + WriteLine("Got already computed model"); + return; + } + Write("Model #" << solutionId << ":\n"); + solutionId++; +#ifdef LOG + for (unsigned i = 0; i < fixedValues.size(); i++) { + unsigned id = fixedValues[i]; + WriteLine("q" + to_string(id_mapping[id]) + " = " + to_string(currentModel[id])); + } +#endif + modelSet.insert(currentModel); + WriteEmptyLine; + } + + static unsigned bvToInt(z3::expr e) { + return (unsigned)e.get_numeral_int(); + } + + void fixed(unsigned id, z3::expr const &e) override { + fixedValues.push_back(id); + unsigned value = bvToInt(e); + currentModel[id_mapping[id]] = value; + } + + user_propagator(z3::solver *s, std::unordered_map& idMapping, unsigned board) + : user_propagator_base(s), board(board), id_mapping(idMapping), currentModel(board, (unsigned)-1) { + + this->register_fixed(); + this->register_final(); + } + + virtual ~user_propagator() = default; + + void push() override { + fixedCnt.push((unsigned) fixedValues.size()); + } + + void pop(unsigned num_scopes) override { + for (unsigned i = 0; i < num_scopes; i++) { + unsigned lastCnt = fixedCnt.top(); + fixedCnt.pop(); + for (auto j = fixedValues.size(); j > lastCnt; j--) { + currentModel[fixedValues[j - 1]] = (unsigned)-1; + } + fixedValues.resize(lastCnt); + } + } + + user_propagator_base *fresh(Z3_context) override { return this; } +}; + +class user_propagator_with_theory : public user_propagator { + +public: + + void fixed(unsigned id, z3::expr const &e) override { + unsigned queenId = id_mapping[id]; + unsigned queenPos = bvToInt(e); + + if (queenPos >= board) { + this->conflict(1, &id); + return; + } + + for (unsigned fixed : fixedValues) { + unsigned otherId = id_mapping[fixed]; + unsigned otherPos = currentModel[fixed]; + + if (queenPos == otherPos) { + const unsigned conflicting[] = {id, fixed}; + this->conflict(2, conflicting); + continue; + } +#ifdef QUEEN + int diffY = abs((int)queenId - (int)otherId); + int diffX = abs((int)queenPos - (int)otherPos); + if (diffX == diffY) { + const unsigned conflicting[] = {id, fixed}; + this->conflict(2, conflicting); + } +#endif + } + + fixedValues.push_back(id); + currentModel[id_mapping[id]] = queenPos; + } + + user_propagator_with_theory(z3::solver *s, std::unordered_map& idMapping, unsigned board) + : user_propagator(s, idMapping, board) {} +}; + +int log2i(unsigned n) { + if (n <= 0) { + return 0; + } + if (n <= 2) { + return 1; + } + unsigned l = 1; + int i = 0; + while (l < n) { + l <<= 1; + i++; + } + return i; +} + +std::vector createQueens(z3::context &context, unsigned num) { + std::vector queens; + int bits = log2i(num) + 1 /*to detect potential overflow in the diagonal*/; + for (unsigned i = 0; i < num; i++) { + queens.push_back(context.bv_const((std::string("q") + to_string(i)).c_str(), bits)); + } + return queens; +} + +void createConstraints(z3::context &context, z3::solver &solver, const std::vector &queens) { + for (unsigned i = 0; i < queens.size(); i++) { + // assert column range + solver.add(z3::uge(queens[i], 0)); + solver.add(z3::ule(queens[i], (int) (queens.size() - 1))); + } + + z3::expr_vector distinct(context); + for (const z3::expr &queen : queens) { + distinct.push_back(queen); + } + + solver.add(z3::distinct(distinct)); + +#ifdef QUEEN + for (unsigned i = 0; i < queens.size(); i++) { + for (unsigned j = i + 1; j < queens.size(); j++) { + solver.add((int)(j - i) != (queens[j] - queens[i])); + solver.add((int)(j - i) != (queens[i] - queens[j])); + } + } +#endif +} + +int test01(unsigned num, bool simple) { + z3::context context; + z3::solver solver(context, !simple ? Z3_mk_solver(context) : Z3_mk_simple_solver(context)); + + std::vector queens = createQueens(context, num); + + createConstraints(context, solver, queens); + + int solutionId = 1; + + while (true) { + z3::check_result res = solver.check(); + + if (res != z3::check_result::sat) { + break; + } + + z3::model model = solver.get_model(); + + WriteLine("Model #" + to_string(solutionId) + ":"); + solutionId++; + + z3::expr_vector blocking(context); + + for (unsigned i = 0; i < num; i++) { + z3::expr eval = model.eval(queens[i]); + WriteLine(("q" + to_string(i) + " = " + to_string(eval.get_numeral_int()))); + blocking.push_back(queens[i] != eval); + } + + solver.add(z3::mk_or(blocking)); + + WriteEmptyLine; + } + return solutionId - 1; +} + +inline int test0(unsigned num) { + return test01(num, false); +} + +inline int test1(unsigned num) { + return test01(num, true); +} + +int test23(unsigned num, bool withTheory) { + z3::context context; + z3::solver solver(context, Z3_mk_simple_solver(context)); + std::unordered_map idMapping; + + user_propagator *propagator; + if (!withTheory) { + propagator = new user_propagator(&solver, idMapping, num); + } + else { + propagator = new user_propagator_with_theory(&solver, idMapping, num); + } + + std::vector queens = createQueens(context, num); + + for (unsigned i = 0; i < queens.size(); i++) { + unsigned id = propagator->add(queens[i]); + idMapping[id] = i; + } + + if (!withTheory) { + createConstraints(context, solver, queens); + } + + solver.check(); + int res = propagator->getModelCount(); + delete propagator; + return res; +} + +inline int test2(unsigned num) { + return test23(num, false); +} + +inline int test3(unsigned num) { + return test23(num, true); +} + +int main() { + + for (int num = 4; num <= 11; num++) { + + std::cout << "num = " << num << ":\n" << std::endl; + + unsigned seed = (unsigned) high_resolution_clock::now().time_since_epoch().count(); + const char *testName[] = + { + "BV + Blocking clauses (Default solver)", + "BV + Blocking clauses (Simple solver)", + "BV + Adding conflicts", + "Custom theory + conflicts", + }; + int permutation[4] = + { + 0, + 1, + 2, + 3, + }; + double timeResults[REPETITIONS * SIZE(permutation)]; + + for (int rep = 0; rep < REPETITIONS; rep++) { + // Execute strategies in a randomised order + std::shuffle(&permutation[0], &permutation[SIZE(permutation) - 1], std::default_random_engine(seed)); + + for (int i : permutation) { + int modelCount = -1; + + auto now1 = high_resolution_clock::now(); + + switch (i) { + case 0: + modelCount = test0(num); + break; + case 1: + modelCount = test1(num); + break; + case 2: + modelCount = test2(num); + break; + case 3: + modelCount = test3(num); + break; + default: + WriteLine("Unknown case"); + break; + } + auto now2 = high_resolution_clock::now(); + duration ms = now2 - now1; + std::cout << testName[i] << " took " << ms.count() << "ms (" << modelCount << " models)" << std::endl; + timeResults[rep * SIZE(permutation) + i] = ms.count(); + WriteLine("-------------"); + } + } + + std::cout << "\n" << std::endl; + + for (unsigned i = 0; i < SIZE(permutation); i++) { + std::cout << testName[i]; + double sum = 0; + for (int j = 0; j < REPETITIONS; j++) { + std::cout << " " << timeResults[j * SIZE(permutation) + i] << "ms"; + sum += timeResults[j * SIZE(permutation) + i]; + } + std::cout << " | avg: " << sum / REPETITIONS << "ms" << std::endl; + } + + std::cout << std::endl; + } +} diff --git a/examples/userPropagator/example.pdf b/examples/userPropagator/example.pdf new file mode 100644 index 000000000..2e802259c Binary files /dev/null and b/examples/userPropagator/example.pdf differ diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 2b4b6179e..e6d34dc3e 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -2014,21 +2014,34 @@ class MLComponent(Component): LIBZ3 = LIBZ3 + ' ' + ' '.join(map(lambda x: '-cclib ' + x, LDFLAGS.split())) + stubs_install_path = '$$(%s printconf path)/stublibs' % OCAMLFIND + if not STATIC_LIB: + loadpath = '-ccopt -L' + stubs_install_path + dllpath = '-dllpath ' + stubs_install_path + LIBZ3 = LIBZ3 + ' ' + loadpath + ' ' + dllpath + if DEBUG_MODE and not(is_cygwin()): # Some ocamlmklib's don't like -g; observed on cygwin, but may be others as well. OCAMLMKLIB += ' -g' z3mls = os.path.join(self.sub_dir, 'z3ml') + LIBZ3ML = '' + if STATIC_LIB: + LIBZ3ML = '-oc ' + os.path.join(self.sub_dir, 'z3ml-static') + out.write('%s.cma: %s %s %s\n' % (z3mls, cmos, stubso, z3linkdep)) - out.write('\t%s -o %s -I %s -L. %s %s %s\n' % (OCAMLMKLIB, z3mls, self.sub_dir, stubso, cmos, LIBZ3)) + out.write('\t%s -o %s %s -I %s -L. %s %s %s\n' % (OCAMLMKLIB, z3mls, LIBZ3ML, self.sub_dir, stubso, cmos, LIBZ3)) out.write('%s.cmxa: %s %s %s %s.cma\n' % (z3mls, cmxs, stubso, z3linkdep, z3mls)) - out.write('\t%s -o %s -I %s -L. %s %s %s\n' % (OCAMLMKLIB, z3mls, self.sub_dir, stubso, cmxs, LIBZ3)) + out.write('\t%s -o %s %s -I %s -L. %s %s %s\n' % (OCAMLMKLIB, z3mls, LIBZ3ML, self.sub_dir, stubso, cmxs, LIBZ3)) out.write('%s.cmxs: %s.cmxa\n' % (z3mls, z3mls)) out.write('\t%s -linkall -shared -o %s.cmxs -I . -I %s %s.cmxa\n' % (OCAMLOPTF, z3mls, self.sub_dir, z3mls)) out.write('\n') out.write('ml: %s.cma %s.cmxa %s.cmxs\n' % (z3mls, z3mls, z3mls)) + if IS_OSX: + out.write('\tinstall_name_tool -id %s/libz3.dylib libz3.dylib\n' % (stubs_install_path)) + out.write('\tinstall_name_tool -change libz3.dylib %s/libz3.dylib api/ml/dllz3ml.so\n' % (stubs_install_path)) out.write('\n') if IS_WINDOWS: @@ -2041,6 +2054,7 @@ class MLComponent(Component): self.mk_uninstall(out) out.write('\n') + # The following three functions may be out of date. def mk_install_deps(self, out): if is_ml_enabled() and self._install_bindings(): out.write(get_component(Z3_DLL_COMPONENT).dll_name + '$(SO_EXT) ') @@ -2286,6 +2300,41 @@ class MLExampleComponent(ExampleComponent): out.write('\n') out.write('_ex_%s: ml_example.byte ml_example$(EXE_EXT)\n\n' % self.name) + debug_opt = '-g ' if DEBUG_MODE else '' + + if STATIC_LIB: + opam_z3_opts = '-thread -package z3-static -linkpkg' + ml_post_install_tests = [ + (OCAMLC, 'ml_example_static.byte'), + (OCAMLC + ' -custom', 'ml_example_static_custom.byte'), + (OCAMLOPT, 'ml_example_static$(EXE_EXT)') + ] + else: + opam_z3_opts = '-thread -package z3 -linkpkg' + ml_post_install_tests = [ + (OCAMLC, 'ml_example_shared.byte'), + (OCAMLC + ' -custom', 'ml_example_shared_custom.byte'), + (OCAMLOPT, 'ml_example_shared$(EXE_EXT)') + ] + + for ocaml_compiler, testname in ml_post_install_tests: + out.write(testname + ':') + for mlfile in get_ml_files(self.ex_dir): + out.write(' %s' % os.path.join(self.to_ex_dir, mlfile)) + out.write('\n') + out.write('\tocamlfind %s -o %s %s %s ' % (ocaml_compiler, debug_opt, testname, opam_z3_opts)) + for mlfile in get_ml_files(self.ex_dir): + out.write(' %s/%s' % (self.to_ex_dir, mlfile)) + out.write('\n') + + if STATIC_LIB: + out.write('_ex_ml_example_post_install: ml_example_static.byte ml_example_static_custom.byte ml_example_static$(EXE_EXT)\n') + else: + out.write('_ex_ml_example_post_install: ml_example_shared.byte ml_example_shared_custom.byte ml_example_shared$(EXE_EXT)\n') + + out.write('\n') + + class PythonExampleComponent(ExampleComponent): def __init__(self, name, path): ExampleComponent.__init__(self, name, path) diff --git a/src/api/api_seq.cpp b/src/api/api_seq.cpp index f0f1d903e..a7eb237b3 100644 --- a/src/api/api_seq.cpp +++ b/src/api/api_seq.cpp @@ -58,7 +58,7 @@ extern "C" { Z3_ast Z3_API Z3_mk_lstring(Z3_context c, unsigned sz, Z3_string str) { Z3_TRY; - LOG_Z3_mk_string(c, str); + LOG_Z3_mk_lstring(c, sz, str); RESET_ERROR_CODE(); unsigned_vector chs; for (unsigned i = 0; i < sz; ++i) chs.push_back((unsigned char)str[i]); @@ -69,6 +69,17 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } + Z3_ast Z3_API Z3_mk_u32string(Z3_context c, unsigned sz, unsigned const chars[]) { + Z3_TRY; + LOG_Z3_mk_u32string(c, sz, chars); + RESET_ERROR_CODE(); + zstring s(sz, chars); + app* a = mk_c(c)->sutil().str.mk_string(s); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(nullptr); + } + Z3_sort Z3_API Z3_mk_string_sort(Z3_context c) { Z3_TRY; LOG_Z3_mk_string_sort(c); @@ -187,10 +198,9 @@ extern "C" { svector buff; for (unsigned i = 0; i < str.length(); ++i) { unsigned ch = str[i]; - if (ch <= 32 || ch >= 127) { + if (ch == 0 || ch >= 256 || (ch == '\\' && i + 1 < str.length() && str[i+1] == 'u')) { buff.reset(); buffer.push_back('\\'); -// buffer.push_back('\\'); // possibly replace by native non-escaped version? buffer.push_back('u'); buffer.push_back('{'); while (ch > 0) { @@ -215,6 +225,38 @@ extern "C" { Z3_CATCH_RETURN(""); } + unsigned Z3_API Z3_get_string_length(Z3_context c, Z3_ast s) { + Z3_TRY; + LOG_Z3_get_string_length(c, s); + RESET_ERROR_CODE(); + zstring str; + if (!mk_c(c)->sutil().str.is_string(to_expr(s), str)) { + SET_ERROR_CODE(Z3_INVALID_ARG, "expression is not a string literal"); + } + return str.length(); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_get_string_contents(Z3_context c, Z3_ast s, unsigned length, unsigned contents[]) { + Z3_TRY; + LOG_Z3_get_string_contents(c, s, length, contents); + RESET_ERROR_CODE(); + zstring str; + if (!mk_c(c)->sutil().str.is_string(to_expr(s), str)) { + SET_ERROR_CODE(Z3_INVALID_ARG, "expression is not a string literal"); + return; + } + if (str.length() != length) { + SET_ERROR_CODE(Z3_INVALID_ARG, "string size disagrees with supplied buffer length"); + return; + } + for (unsigned i = 0; i < length; ++i) + contents[i] = str[i]; + + Z3_CATCH; + } + + #define MK_SORTED(NAME, FN ) \ Z3_ast Z3_API NAME(Z3_context c, Z3_sort s) { \ Z3_TRY; \ diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 2837d6a36..659e4c4a4 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -875,7 +875,7 @@ extern "C" { init_solver(c, s); solver::push_eh_t _push = push_eh; solver::pop_eh_t _pop = pop_eh; - solver::fresh_eh_t _fresh = [&](void * user_ctx, ast_manager& m, solver::context_obj*& _ctx) { + solver::fresh_eh_t _fresh = [=](void * user_ctx, ast_manager& m, solver::context_obj*& _ctx) { ast_context_params params; params.set_foreign_manager(&m); auto* ctx = alloc(api::context, ¶ms, false); diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index f0c0cc3aa..51f88a3ea 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -36,12 +36,12 @@ Notes: \defgroup cppapi C++ API */ -/*@{*/ +/**@{*/ /** @name C++ API classes and functions */ -/*@{*/ +/**@{*/ /** \brief Z3 C++ namespace @@ -379,6 +379,7 @@ namespace z3 { expr string_val(char const* s); expr string_val(char const* s, unsigned n); expr string_val(std::string const& s); + expr string_val(std::u32string const& s); expr num_val(int n, sort const & s); @@ -1100,23 +1101,29 @@ namespace z3 { bool is_string_value() const { return Z3_is_string(ctx(), m_ast); } /** - \brief for a string value expression return an escaped or unescaped string value. + \brief for a string value expression return an escaped string value. \pre expression is for a string value. */ - std::string get_escaped_string() const { + std::string get_string() const { assert(is_string_value()); char const* s = Z3_get_string(ctx(), m_ast); check_error(); return std::string(s); } - std::string get_string() const { + /** + \brief for a string value expression return an unespaced string value. + \pre expression is for a string value. + */ + + std::u32string get_u32string() const { assert(is_string_value()); - unsigned n; - char const* s = Z3_get_lstring(ctx(), m_ast, &n); - check_error(); - return std::string(s, n); + unsigned n = Z3_get_string_length(ctx(), m_ast); + std::u32string s; + s.resize(n); + Z3_get_string_contents(ctx(), m_ast, n, (unsigned*)s.data()); + return s; } operator Z3_app() const { assert(is_app()); return reinterpret_cast(m_ast); } @@ -1516,7 +1523,7 @@ namespace z3 { expr substitute(expr_vector const& dst); - class iterator { + class iterator { expr& e; unsigned i; public: @@ -1912,14 +1919,14 @@ namespace z3 { Z3_ast r; if (a.is_int()) { expr zero = a.ctx().int_val(0); - expr ge = a >= zero; - expr na = -a; + expr ge = a >= zero; + expr na = -a; r = Z3_mk_ite(a.ctx(), ge, a, na); } else if (a.is_real()) { expr zero = a.ctx().real_val(0); - expr ge = a >= zero; - expr na = -a; + expr ge = a >= zero; + expr na = -a; r = Z3_mk_ite(a.ctx(), ge, a, na); } else { @@ -3480,6 +3487,7 @@ namespace z3 { inline expr context::string_val(char const* s, unsigned n) { Z3_ast r = Z3_mk_lstring(m_ctx, n, s); check_error(); return expr(*this, r); } inline expr context::string_val(char const* s) { Z3_ast r = Z3_mk_string(m_ctx, s); check_error(); return expr(*this, r); } inline expr context::string_val(std::string const& s) { Z3_ast r = Z3_mk_string(m_ctx, s.c_str()); check_error(); return expr(*this, r); } + inline expr context::string_val(std::u32string const& s) { Z3_ast r = Z3_mk_u32string(m_ctx, (unsigned)s.size(), (unsigned const*)s.c_str()); check_error(); return expr(*this, r); } inline expr context::num_val(int n, sort const & s) { Z3_ast r = Z3_mk_int(m_ctx, n, s); check_error(); return expr(*this, r); } @@ -3932,6 +3940,8 @@ namespace z3 { virtual void push() = 0; virtual void pop(unsigned num_scopes) = 0; + virtual ~user_propagator_base() = default; + /** \brief user_propagators created using \c fresh() are created during search and their lifetimes are restricted to search time. They should @@ -3954,12 +3964,28 @@ namespace z3 { Z3_solver_propagate_fixed(ctx(), *s, fixed_eh); } + void register_fixed() { + assert(s); + m_fixed_eh = [this](unsigned id, expr const& e) { + fixed(id, e); + }; + Z3_solver_propagate_fixed(ctx(), *s, fixed_eh); + } + void register_eq(eq_eh_t& f) { assert(s); m_eq_eh = f; Z3_solver_propagate_eq(ctx(), *s, eq_eh); } + void register_eq() { + assert(s); + m_eq_eh = [this](unsigned x, unsigned y) { + eq(x, y); + }; + Z3_solver_propagate_eq(ctx(), *s, eq_eh); + } + /** \brief register a callback on final-check. During the final check stage, all propagations have been processed. @@ -3973,6 +3999,21 @@ namespace z3 { m_final_eh = f; Z3_solver_propagate_final(ctx(), *s, final_eh); } + + void register_final() { + assert(s); + m_final_eh = [this]() { + final(); + }; + Z3_solver_propagate_final(ctx(), *s, final_eh); + } + + + virtual void fixed(unsigned /*id*/, expr const& /*e*/) { } + + virtual void eq(unsigned /*x*/, unsigned /*y*/) { } + + virtual void final() { } /** \brief tracks \c e by a unique identifier that is returned by the call. @@ -4020,7 +4061,7 @@ namespace z3 { } -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ #undef Z3_THROW diff --git a/src/api/julia/z3jl.cpp b/src/api/julia/z3jl.cpp index ccb2f7d07..73e4356b2 100644 --- a/src/api/julia/z3jl.cpp +++ b/src/api/julia/z3jl.cpp @@ -214,7 +214,6 @@ JLCXX_MODULE define_julia_module(jlcxx::Module &m) .MM(expr, numerator) .MM(expr, denominator) .MM(expr, is_string_value) - .MM(expr, get_escaped_string) .MM(expr, get_string) .MM(expr, decl) .MM(expr, num_args) diff --git a/src/api/ml/z3native_stubs.c.pre b/src/api/ml/z3native_stubs.c.pre index 5c1a3bd06..895e20235 100644 --- a/src/api/ml/z3native_stubs.c.pre +++ b/src/api/ml/z3native_stubs.c.pre @@ -76,14 +76,14 @@ int compare_pointers(void* pt1, void* pt2) { return +1; } -#define MK_CTX_OF(X) \ +#define MK_CTX_OF(X, USED) \ CAMLprim DLL_PUBLIC value n_context_of_ ## X(value v) { \ CAMLparam1(v); \ CAMLlocal1(result); \ Z3_context_plus cp; \ Z3_ ## X ## _plus * p = (Z3_ ## X ## _plus *) Data_custom_val(v); \ cp = p->cp; \ - result = caml_alloc_custom(&Z3_context_plus_custom_ops, sizeof(Z3_context_plus), 0, 1); \ + result = caml_alloc_custom_mem(&Z3_context_plus_custom_ops, sizeof(Z3_context_plus), USED); \ *(Z3_context_plus *)Data_custom_val(result) = cp; \ /* We increment the usage counter of the context, as we just \ created a second custom block holding that context */ \ @@ -102,7 +102,7 @@ int compare_pointers(void* pt1, void* pt2) { CAMLlocal1(result); \ Z3_context_plus cp = *(Z3_context_plus*)(Data_custom_val(v)); \ Z3_ ## X ## _plus a = Z3_ ## X ## _plus_mk(cp, NULL); \ - result = caml_alloc_custom(&Z3_ ## X ## _plus_custom_ops, sizeof(Z3_ ## X ## _plus), 0, 1); \ + result = caml_alloc_custom_mem(&Z3_ ## X ## _plus_custom_ops, sizeof(Z3_ ## X ## _plus), USED); \ *(Z3_ ## X ## _plus*)(Data_custom_val(result)) = a; \ CAMLreturn(result); \ } @@ -294,9 +294,9 @@ static struct custom_operations Z3_ast_plus_custom_ops = { Z3_ast_compare_ext }; -MK_CTX_OF(ast) +MK_CTX_OF(ast, 16) // let's say 16 bytes per ast -#define MK_PLUS_OBJ_NO_REF(X) \ +#define MK_PLUS_OBJ_NO_REF(X, USED) \ typedef struct { \ Z3_context_plus cp; \ Z3_ ## X p; \ @@ -349,9 +349,9 @@ MK_CTX_OF(ast) Z3_ ## X ## _compare_ext \ }; \ \ - MK_CTX_OF(X) + MK_CTX_OF(X, USED) -#define MK_PLUS_OBJ(X) \ +#define MK_PLUS_OBJ(X, USED) \ typedef struct { \ Z3_context_plus cp; \ Z3_ ## X p; \ @@ -408,27 +408,27 @@ MK_CTX_OF(ast) Z3_ ## X ## _compare_ext \ }; \ \ - MK_CTX_OF(X) + MK_CTX_OF(X, USED) -MK_PLUS_OBJ_NO_REF(symbol) -MK_PLUS_OBJ_NO_REF(constructor) -MK_PLUS_OBJ_NO_REF(constructor_list) -MK_PLUS_OBJ_NO_REF(rcf_num) -MK_PLUS_OBJ(params) -MK_PLUS_OBJ(param_descrs) -MK_PLUS_OBJ(model) -MK_PLUS_OBJ(func_interp) -MK_PLUS_OBJ(func_entry) -MK_PLUS_OBJ(goal) -MK_PLUS_OBJ(tactic) -MK_PLUS_OBJ(probe) -MK_PLUS_OBJ(apply_result) -MK_PLUS_OBJ(solver) -MK_PLUS_OBJ(stats) -MK_PLUS_OBJ(ast_map) -MK_PLUS_OBJ(ast_vector) -MK_PLUS_OBJ(fixedpoint) -MK_PLUS_OBJ(optimize) +MK_PLUS_OBJ_NO_REF(symbol, 32) +MK_PLUS_OBJ_NO_REF(constructor, 32) +MK_PLUS_OBJ_NO_REF(constructor_list, 32) +MK_PLUS_OBJ_NO_REF(rcf_num, 32) +MK_PLUS_OBJ(params, 128) +MK_PLUS_OBJ(param_descrs, 128) +MK_PLUS_OBJ(model, 512) +MK_PLUS_OBJ(func_interp, 128) +MK_PLUS_OBJ(func_entry, 128) +MK_PLUS_OBJ(goal, 128) +MK_PLUS_OBJ(tactic, 128) +MK_PLUS_OBJ(probe, 128) +MK_PLUS_OBJ(apply_result, 128) +MK_PLUS_OBJ(solver, 20 * 1000 * 1000) // pretend a solver is 20MB +MK_PLUS_OBJ(stats, 128) +MK_PLUS_OBJ(ast_map, 1024 * 2) +MK_PLUS_OBJ(ast_vector, 128) +MK_PLUS_OBJ(fixedpoint, 20 * 1000 * 1000) +MK_PLUS_OBJ(optimize, 20 * 1000 * 1000) #ifdef __cplusplus extern "C" { diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 5c0d5765a..8e6a86cf1 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -11102,33 +11102,34 @@ class PropClosures: self.bases = {} self.lock = None - def set_threaded(): + def set_threaded(self): if self.lock is None: import threading - self.lock = threading.thread.Lock() + self.lock = threading.Lock() def get(self, ctx): if self.lock: - self.lock.acquire() - r = self.bases[ctx] - if self.lock: - self.lock.release() + with self.lock: + r = self.bases[ctx] + else: + r = self.bases[ctx] return r def set(self, ctx, r): if self.lock: - self.lock.acquire() - self.bases[ctx] = r - if self.lock: - self.lock.release() + with self.lock: + self.bases[ctx] = r + else: + self.bases[ctx] = r def insert(self, r): if self.lock: - self.lock.acquire() - id = len(self.bases) + 3 - self.bases[id] = r - if self.lock: - self.lock.release() + with self.lock: + id = len(self.bases) + 3 + self.bases[id] = r + else: + id = len(self.bases) + 3 + self.bases[id] = r return id @@ -11151,8 +11152,9 @@ def user_prop_pop(ctx, num_scopes): def user_prop_fresh(id, ctx): _prop_closures.set_threaded() - new_prop = UsePropagateBase(None, ctx) - _prop_closures.set(new_prop.id, new_prop.fresh()) + prop = _prop_closures.get(id) + new_prop = prop.fresh() + _prop_closures.set(new_prop.id, new_prop) return ctypes.c_void_p(new_prop.id) @@ -11214,11 +11216,12 @@ class UserPropagateBase: self.eq = None self.diseq = None if ctx: + # TBD fresh is broken: ctx is not of the right type when we reach here. self._ctx = Context() - Z3_del_context(self._ctx.ctx) - self._ctx.ctx = ctx - self._ctx.eh = Z3_set_error_handler(ctx, z3_error_handler) - Z3_set_ast_print_mode(ctx, Z3_PRINT_SMTLIB2_COMPLIANT) + #Z3_del_context(self._ctx.ctx) + #self._ctx.ctx = ctx + #self._ctx.eh = Z3_set_error_handler(ctx, z3_error_handler) + #Z3_set_ast_print_mode(ctx, Z3_PRINT_SMTLIB2_COMPLIANT) if s: Z3_solver_propagate_init(self.ctx_ref(), s.solver, diff --git a/src/api/z3_algebraic.h b/src/api/z3_algebraic.h index 952692efa..ee8ee21b8 100644 --- a/src/api/z3_algebraic.h +++ b/src/api/z3_algebraic.h @@ -25,10 +25,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Algebraic Numbers */ - /*@{*/ + /**@{*/ /** \brief Return \c true if \c a can be used as value in the Z3 real algebraic number package. @@ -240,8 +240,8 @@ extern "C" { */ unsigned Z3_API Z3_algebraic_get_i(Z3_context c, Z3_ast a); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_api.h b/src/api/z3_api.h index a15e77788..7d1366329 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -37,11 +37,11 @@ DEFINE_TYPE(Z3_optimize); DEFINE_TYPE(Z3_rcf_num); /** \defgroup capi C API */ -/*@{*/ +/**@{*/ /** @name Types */ -///@{ +/**@{*/ /** Most of the types in the C API are opaque pointers. @@ -1449,7 +1449,7 @@ typedef enum Z3_GOAL_UNDER_OVER } Z3_goal_prec; -///@} +/**@}*/ #ifdef __cplusplus extern "C" { @@ -1514,7 +1514,7 @@ extern "C" { /**@}*/ /** @name Create configuration */ - /*@{*/ + /**@{*/ /** \brief Create a configuration object for the Z3 context object. @@ -1569,10 +1569,10 @@ extern "C" { */ void Z3_API Z3_set_param_value(Z3_config c, Z3_string param_id, Z3_string param_value); - /*@}*/ + /**@}*/ /** @name Context and AST Reference Counting */ - /*@{*/ + /**@{*/ /** \brief Create a context using the given configuration. @@ -1678,10 +1678,10 @@ extern "C" { void Z3_API Z3_interrupt(Z3_context c); - /*@}*/ + /**@}*/ /** @name Parameters */ - /*@{*/ + /**@{*/ /** \brief Create a Z3 (empty) parameter set. @@ -1754,10 +1754,10 @@ extern "C" { */ void Z3_API Z3_params_validate(Z3_context c, Z3_params p, Z3_param_descrs d); - /*@}*/ + /**@}*/ /** @name Parameter Descriptions */ - /*@{*/ + /**@{*/ /** \brief Increment the reference counter of the given parameter description set. @@ -1811,10 +1811,10 @@ extern "C" { */ Z3_string Z3_API Z3_param_descrs_to_string(Z3_context c, Z3_param_descrs p); - /*@}*/ + /**@}*/ /** @name Symbols */ - /*@{*/ + /**@{*/ /** \brief Create a Z3 symbol using an integer. @@ -1843,10 +1843,10 @@ extern "C" { */ Z3_symbol Z3_API Z3_mk_string_symbol(Z3_context c, Z3_string s); - /*@}*/ + /**@}*/ /** @name Sorts */ - /*@{*/ + /**@{*/ /** \brief Create a free (uninterpreted) type using the given name (symbol). @@ -2150,10 +2150,10 @@ extern "C" { Z3_func_decl* tester, Z3_func_decl accessors[]); - /*@}*/ + /**@}*/ /** @name Constants and Applications */ - /*@{*/ + /**@{*/ /** \brief Declare a constant or function. @@ -2287,10 +2287,10 @@ extern "C" { */ void Z3_API Z3_add_rec_def(Z3_context c, Z3_func_decl f, unsigned n, Z3_ast args[], Z3_ast body); - /*@}*/ + /**@}*/ /** @name Propositional Logic and Equality */ - /*@{*/ + /**@{*/ /** \brief Create an AST node representing \c true. @@ -2397,10 +2397,10 @@ extern "C" { def_API('Z3_mk_or', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) */ Z3_ast Z3_API Z3_mk_or(Z3_context c, unsigned num_args, Z3_ast const args[]); - /*@}*/ + /**@}*/ /** @name Integers and Reals */ - /*@{*/ + /**@{*/ /** \brief Create an AST node representing \ccode{args[0] + ... + args[num_args-1]}. @@ -2573,10 +2573,10 @@ extern "C" { def_API('Z3_mk_is_int', AST, (_in(CONTEXT), _in(AST))) */ Z3_ast Z3_API Z3_mk_is_int(Z3_context c, Z3_ast t1); - /*@}*/ + /**@}*/ /** @name Bit-vectors */ - /*@{*/ + /**@{*/ /** \brief Bitwise negation. @@ -3097,10 +3097,10 @@ extern "C" { def_API('Z3_mk_bvmul_no_underflow', AST, (_in(CONTEXT), _in(AST), _in(AST))) */ Z3_ast Z3_API Z3_mk_bvmul_no_underflow(Z3_context c, Z3_ast t1, Z3_ast t2); - /*@}*/ + /**@}*/ /** @name Arrays */ - /*@{*/ + /**@{*/ /** \brief Array read. The argument \c a is the array and \c i is the index of the array that gets read. @@ -3212,10 +3212,10 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_set_has_size(Z3_context c, Z3_ast set, Z3_ast k); - /*@}*/ + /**@}*/ /** @name Sets */ - /*@{*/ + /**@{*/ /** \brief Create Set type. @@ -3308,10 +3308,10 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_array_ext(Z3_context c, Z3_ast arg1, Z3_ast arg2); - /*@}*/ + /**@}*/ /** @name Numerals */ - /*@{*/ + /**@{*/ /** \brief Create a numeral of a given sort. @@ -3400,10 +3400,10 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_bv_numeral(Z3_context c, unsigned sz, bool const* bits); - /*@}*/ + /**@}*/ /** @name Sequences and regular expressions */ - /*@{*/ + /**@{*/ /** \brief Create a sequence sort out of the sort for the elements. @@ -3480,6 +3480,11 @@ extern "C" { /** \brief Create a string constant out of the string that is passed in + The string may contain escape encoding for non-printable characters + or characters outside of the basic printable ASCII range. For example, + the escape encoding \u{0} represents the character 0 and the encoding + \u{100} represents the character 256. + def_API('Z3_mk_string', AST, (_in(CONTEXT), _in(STRING))) */ Z3_ast Z3_API Z3_mk_string(Z3_context c, Z3_string s); @@ -3487,12 +3492,22 @@ extern "C" { /** \brief Create a string constant out of the string that is passed in It takes the length of the string as well to take into account - 0 characters. The string is unescaped. + 0 characters. The string is treated as if it is unescaped so a sequence + of characters \u{0} is treated as 5 characters and not the character 0. def_API('Z3_mk_lstring', AST, (_in(CONTEXT), _in(UINT), _in(STRING))) */ Z3_ast Z3_API Z3_mk_lstring(Z3_context c, unsigned len, Z3_string s); + /** + \brief Create a string constant out of the string that is passed in + It takes the length of the string as well to take into account + 0 characters. The string is unescaped. + + def_API('Z3_mk_u32string', AST, (_in(CONTEXT), _in(UINT), _in_array(1, UINT))) + */ + Z3_ast Z3_API Z3_mk_u32string(Z3_context c, unsigned len, unsigned const chars[]); + /** \brief Determine if \c s is a string constant. @@ -3502,6 +3517,7 @@ extern "C" { /** \brief Retrieve the string constant stored in \c s. + Characters outside the basic printiable ASCII range are escaped. \pre Z3_is_string(c, s) @@ -3510,14 +3526,36 @@ extern "C" { Z3_string Z3_API Z3_get_string(Z3_context c, Z3_ast s); /** - \brief Retrieve the unescaped string constant stored in \c s. + \brief Retrieve the string constant stored in \c s. The string can contain escape sequences. + Characters in the range 1 to 255 are literal. + Characters in the range 0, and 256 above are escaped. \pre Z3_is_string(c, s) def_API('Z3_get_lstring', CHAR_PTR, (_in(CONTEXT), _in(AST), _out(UINT))) */ Z3_char_ptr Z3_API Z3_get_lstring(Z3_context c, Z3_ast s, unsigned* length); + + /** + \brief Retrieve the length of the unescaped string constant stored in \c s. + \pre Z3_is_string(c, s) + + def_API('Z3_get_string_length', UINT, (_in(CONTEXT), _in(AST))) + */ + unsigned Z3_API Z3_get_string_length(Z3_context c, Z3_ast s); + + /** + \brief Retrieve the unescaped string constant stored in \c s. + + \pre Z3_is_string(c, s) + + \pre length contains the number of characters in s + + def_API('Z3_get_string_contents', VOID, (_in(CONTEXT), _in(AST), _in(UINT), _out_array(2, UINT))) + */ + void Z3_API Z3_get_string_contents(Z3_context c, Z3_ast s, unsigned length, unsigned contents[]); + /** \brief Create an empty sequence of the sequence sort \c seq. @@ -3821,11 +3859,11 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_char_is_digit(Z3_context c, Z3_ast ch); - /*@}*/ + /**@}*/ /** @name Special relations */ - /*@{*/ + /**@{*/ /** \brief create a linear ordering relation over signature \c a. The relation is identified by the index \c id. @@ -3866,10 +3904,10 @@ extern "C" { */ Z3_func_decl Z3_API Z3_mk_transitive_closure(Z3_context c, Z3_func_decl f); - /*@}*/ + /**@}*/ /** @name Quantifiers */ - /*@{*/ + /**@{*/ /** \brief Create a pattern for quantifier instantiation. @@ -4174,10 +4212,10 @@ extern "C" { Z3_ast body); - /*@}*/ + /**@}*/ /** @name Accessors */ - /*@{*/ + /**@{*/ /** \brief Return \c Z3_INT_SYMBOL if the symbol was constructed using #Z3_mk_int_symbol, and \c Z3_STRING_SYMBOL if the symbol @@ -5112,10 +5150,10 @@ extern "C" { def_API('Z3_simplify_get_param_descrs', PARAM_DESCRS, (_in(CONTEXT),)) */ Z3_param_descrs Z3_API Z3_simplify_get_param_descrs(Z3_context c); - /*@}*/ + /**@}*/ /** @name Modifiers */ - /*@{*/ + /**@{*/ /** \brief Update the arguments of term \c a using the arguments \c args. The number of arguments \c num_args should coincide @@ -5158,10 +5196,10 @@ extern "C" { def_API('Z3_translate', AST, (_in(CONTEXT), _in(AST), _in(CONTEXT))) */ Z3_ast Z3_API Z3_translate(Z3_context source, Z3_ast a, Z3_context target); - /*@}*/ + /**@}*/ /** @name Models */ - /*@{*/ + /**@{*/ /** \brief Create a fresh model object. It has reference count 0. @@ -5496,10 +5534,10 @@ extern "C" { def_API('Z3_func_entry_get_arg', AST, (_in(CONTEXT), _in(FUNC_ENTRY), _in(UINT))) */ Z3_ast Z3_API Z3_func_entry_get_arg(Z3_context c, Z3_func_entry e, unsigned i); - /*@}*/ + /**@}*/ /** @name Interaction logging */ - /*@{*/ + /**@{*/ /** \brief Log interaction to a file. @@ -5534,10 +5572,10 @@ extern "C" { def_API('Z3_toggle_warning_messages', VOID, (_in(BOOL),)) */ void Z3_API Z3_toggle_warning_messages(bool enabled); - /*@}*/ + /**@}*/ /** @name String conversion */ - /*@{*/ + /**@{*/ /** \brief Select mode for the format used for pretty-printing AST nodes. @@ -5624,10 +5662,10 @@ extern "C" { Z3_ast const assumptions[], Z3_ast formula); - /*@}*/ + /**@}*/ /** @name Parser interface */ - /*@{*/ + /**@{*/ /** \brief Parse the given string using the SMT-LIB2 parser. @@ -5671,10 +5709,10 @@ extern "C" { Z3_string Z3_API Z3_eval_smtlib2_string(Z3_context, Z3_string str); - /*@}*/ + /**@}*/ /** @name Error Handling */ - /*@{*/ + /**@{*/ #ifndef SAFE_ERRORS /** \brief Return the error code for the last API call. @@ -5717,10 +5755,10 @@ extern "C" { */ Z3_string Z3_API Z3_get_error_msg(Z3_context c, Z3_error_code err); - /*@}*/ + /**@}*/ /** @name Miscellaneous */ - /*@{*/ + /**@{*/ /** \brief Return Z3 version number information. @@ -5781,10 +5819,10 @@ extern "C" { def_API('Z3_finalize_memory', VOID, ()) */ void Z3_API Z3_finalize_memory(void); - /*@}*/ + /**@}*/ /** @name Goals */ - /*@{*/ + /**@{*/ /** \brief Create a goal (aka problem). A goal is essentially a set of formulas, that can be solved and/or transformed using @@ -5934,10 +5972,10 @@ extern "C" { */ Z3_string Z3_API Z3_goal_to_dimacs_string(Z3_context c, Z3_goal g, bool include_names); - /*@}*/ + /**@}*/ /** @name Tactics and Probes */ - /*@{*/ + /**@{*/ /** \brief Return a tactic associated with the given name. The complete list of tactics may be obtained using the procedures #Z3_get_num_tactics and #Z3_get_tactic_name. @@ -6286,10 +6324,10 @@ extern "C" { */ Z3_goal Z3_API Z3_apply_result_get_subgoal(Z3_context c, Z3_apply_result r, unsigned i); - /*@}*/ + /**@}*/ /** @name Solvers*/ - /*@{*/ + /**@{*/ /** \brief Create a new solver. This solver is a "combined solver" (see combined_solver module) that internally uses a non-incremental (solver1) and an @@ -6812,10 +6850,10 @@ extern "C" { */ Z3_string Z3_API Z3_solver_to_dimacs_string(Z3_context c, Z3_solver s, bool include_names); - /*@}*/ + /**@}*/ /** @name Statistics */ - /*@{*/ + /**@{*/ /** \brief Convert a statistics into a string. @@ -6897,11 +6935,11 @@ extern "C" { */ uint64_t Z3_API Z3_get_estimated_alloc_size(void); - /*@}*/ + /**@}*/ #ifdef __cplusplus } #endif // __cplusplus -/*@}*/ +/**@}*/ diff --git a/src/api/z3_ast_containers.h b/src/api/z3_ast_containers.h index e317460f8..1db7bf4ce 100644 --- a/src/api/z3_ast_containers.h +++ b/src/api/z3_ast_containers.h @@ -23,10 +23,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name AST vectors */ - /*@{*/ + /**@{*/ /** \brief Return an empty AST vector. @@ -104,10 +104,10 @@ extern "C" { */ Z3_string Z3_API Z3_ast_vector_to_string(Z3_context c, Z3_ast_vector v); - /*@}*/ + /**@}*/ /** @name AST maps */ - /*@{*/ + /**@{*/ /** \brief Return an empty mapping from AST to AST @@ -189,8 +189,8 @@ extern "C" { def_API('Z3_ast_map_to_string', STRING, (_in(CONTEXT), _in(AST_MAP))) */ Z3_string Z3_API Z3_ast_map_to_string(Z3_context c, Z3_ast_map m); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_fixedpoint.h b/src/api/z3_fixedpoint.h index b9fada51d..5eadaaf46 100644 --- a/src/api/z3_fixedpoint.h +++ b/src/api/z3_fixedpoint.h @@ -23,10 +23,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Fixedpoint facilities */ - /*@{*/ + /**@{*/ /** \brief Create a new fixedpoint context. @@ -373,8 +373,8 @@ extern "C" { void Z3_API Z3_fixedpoint_add_constraint (Z3_context c, Z3_fixedpoint d, Z3_ast e, unsigned lvl); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_fpa.h b/src/api/z3_fpa.h index fe85e702e..4ab0d9177 100644 --- a/src/api/z3_fpa.h +++ b/src/api/z3_fpa.h @@ -23,10 +23,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Floating-Point Arithmetic */ - /*@{*/ + /**@{*/ /** \brief Create the RoundingMode sort. @@ -841,7 +841,7 @@ extern "C" { /** @name Z3-specific floating-point extensions */ - /*@{*/ + /**@{*/ /** \brief Retrieves the number of bits reserved for the exponent in a FloatingPoint sort. @@ -1080,9 +1080,9 @@ extern "C" { def_API('Z3_mk_fpa_to_fp_int_real', AST, (_in(CONTEXT),_in(AST),_in(AST),_in(AST),_in(SORT))) */ Z3_ast Z3_API Z3_mk_fpa_to_fp_int_real(Z3_context c, Z3_ast rm, Z3_ast exp, Z3_ast sig, Z3_sort s); - /*@}*/ - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_optimization.h b/src/api/z3_optimization.h index 51d8853e0..3cdacc46d 100644 --- a/src/api/z3_optimization.h +++ b/src/api/z3_optimization.h @@ -28,10 +28,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Optimization facilities */ - /*@{*/ + /**@{*/ /** \brief Create a new optimize context. @@ -368,8 +368,8 @@ extern "C" { Z3_model_eh model_eh); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_polynomial.h b/src/api/z3_polynomial.h index 47979fc87..6e7a72df3 100644 --- a/src/api/z3_polynomial.h +++ b/src/api/z3_polynomial.h @@ -24,11 +24,11 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Polynomials */ - /*@{*/ + /**@{*/ /** \brief Return the nonzero subresultants of \c p and \c q with respect to the "variable" \c x. @@ -43,8 +43,8 @@ extern "C" { Z3_ast_vector Z3_API Z3_polynomial_subresultants(Z3_context c, Z3_ast p, Z3_ast q, Z3_ast x); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_rcf.h b/src/api/z3_rcf.h index f61da20c5..88c27db61 100644 --- a/src/api/z3_rcf.h +++ b/src/api/z3_rcf.h @@ -26,10 +26,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Real Closed Fields */ - /*@{*/ + /**@{*/ /** \brief Delete a RCF numeral created using the RCF API. @@ -196,8 +196,8 @@ extern "C" { */ void Z3_API Z3_rcf_get_numerator_denominator(Z3_context c, Z3_rcf_num a, Z3_rcf_num * n, Z3_rcf_num * d); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/z3_spacer.h b/src/api/z3_spacer.h index fe4086e5a..dd1028433 100644 --- a/src/api/z3_spacer.h +++ b/src/api/z3_spacer.h @@ -23,10 +23,10 @@ extern "C" { #endif // __cplusplus /** \defgroup capi C API */ - /*@{*/ + /**@{*/ /** @name Spacer facilities */ - /*@{*/ + /**@{*/ /** \brief Pose a query against the asserted rules at the given level. @@ -132,8 +132,8 @@ extern "C" { Z3_ast_vector vars, Z3_ast body); - /*@}*/ - /*@}*/ + /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/ast/ast.h b/src/ast/ast.h index 0fafc2a76..739f63dc8 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1399,6 +1399,7 @@ inline bool has_labels(expr const * n) { class some_value_proc { public: virtual expr * operator()(sort * s) = 0; + virtual ~some_value_proc() = default; }; // ----------------------------------- diff --git a/src/ast/ast_smt_pp.h b/src/ast/ast_smt_pp.h index 23b656c12..ff25f1235 100644 --- a/src/ast/ast_smt_pp.h +++ b/src/ast/ast_smt_pp.h @@ -46,6 +46,7 @@ public: public: virtual bool operator()(func_decl* d) const { return false; } virtual bool operator()(sort* s) const { return false; } + virtual ~is_declared() = default; }; private: ast_manager& m_manager; diff --git a/src/ast/datatype_decl_plugin.cpp b/src/ast/datatype_decl_plugin.cpp index 084f7d2c7..66ce1a3ba 100644 --- a/src/ast/datatype_decl_plugin.cpp +++ b/src/ast/datatype_decl_plugin.cpp @@ -406,7 +406,7 @@ namespace datatype { VALIDATE_PARAM(arity == 1 && num_parameters == 1 && parameters[0].is_ast() && is_func_decl(parameters[0].get_ast())); VALIDATE_PARAM(u().is_datatype(domain[0])); VALIDATE_PARAM_PP(domain[0] == to_func_decl(parameters[0].get_ast())->get_range(), "invalid sort argument passed to recognizer"); - // blindly trust that parameter is a constructor + VALIDATE_PARAM_PP(u().is_constructor(to_func_decl(parameters[0].get_ast())), "expecting constructor argument to recognizer"); sort* range = m_manager->mk_bool_sort(); func_decl_info info(m_family_id, OP_DT_IS, num_parameters, parameters); info.m_private_parameters = true; diff --git a/src/ast/euf/euf_egraph.cpp b/src/ast/euf/euf_egraph.cpp index f5ad2a6f4..1f8e14565 100644 --- a/src/ast/euf/euf_egraph.cpp +++ b/src/ast/euf/euf_egraph.cpp @@ -681,6 +681,7 @@ namespace euf { void egraph::begin_explain() { SASSERT(m_todo.empty()); m_uses_congruence = false; + DEBUG_CODE(for (enode* n : m_nodes) SASSERT(!n->is_marked1());); } void egraph::end_explain() { diff --git a/src/ast/fpa/bv2fpa_converter.cpp b/src/ast/fpa/bv2fpa_converter.cpp index 49f9abacd..3da45781f 100644 --- a/src/ast/fpa/bv2fpa_converter.cpp +++ b/src/ast/fpa/bv2fpa_converter.cpp @@ -102,7 +102,7 @@ expr_ref bv2fpa_converter::convert_bv2fp(sort * s, expr * sgn, expr * exp, expr rational exp_unbiased_q; exp_unbiased_q = exp_q - m_fpa_util.fm().m_powers2.m1(ebits - 1); - scoped_mpz sig_z(mpzm); + scoped_mpz sig_z(mpzm); mpf_exp_t exp_z; mpzm.set(sig_z, sig_q.to_mpq().numerator()); exp_z = mpzm.get_int64(exp_unbiased_q.to_mpq().numerator()); @@ -346,7 +346,7 @@ void bv2fpa_converter::convert_consts(model_core * mc, model_core * target_model app * a0 = to_app(val->get_arg(0)); expr_ref v0(m), v1(m), v2(m); -#ifdef Z3DEBUG +#ifdef Z3DEBUG_FPA2BV_NAMES app * a1 = to_app(val->get_arg(1)); app * a2 = to_app(val->get_arg(2)); v0 = mc->get_const_interp(a0->get_decl()); @@ -378,7 +378,7 @@ void bv2fpa_converter::convert_consts(model_core * mc, model_core * target_model SASSERT(val->is_app_of(m_fpa_util.get_family_id(), OP_FPA_FP)); -#ifdef Z3DEBUG +#ifdef Z3DEBUG_FPA2BV_NAMES SASSERT(to_app(val->get_arg(0))->get_decl()->get_arity() == 0); SASSERT(to_app(val->get_arg(1))->get_decl()->get_arity() == 0); SASSERT(to_app(val->get_arg(2))->get_decl()->get_arity() == 0); @@ -386,9 +386,10 @@ void bv2fpa_converter::convert_consts(model_core * mc, model_core * target_model seen.insert(to_app(val->get_arg(1))->get_decl()); seen.insert(to_app(val->get_arg(2))->get_decl()); #else - SASSERT(a->get_arg(0)->get_kind() == OP_EXTRACT); - SASSERT(to_app(a->get_arg(0))->get_arg(0)->get_kind() == OP_EXTRACT); + SASSERT(is_app(val->get_arg(0))); + SASSERT(m_bv_util.is_extract(val->get_arg(0))); seen.insert(to_app(to_app(val->get_arg(0))->get_arg(0))->get_decl()); + #endif if (!sgn && !sig && !exp) diff --git a/src/ast/fpa/fpa2bv_converter.cpp b/src/ast/fpa/fpa2bv_converter.cpp index de0b69946..7876d2d79 100644 --- a/src/ast/fpa/fpa2bv_converter.cpp +++ b/src/ast/fpa/fpa2bv_converter.cpp @@ -192,7 +192,7 @@ void fpa2bv_converter::mk_const(func_decl * f, expr_ref & result) { app_ref sgn(m), s(m), e(m); -#ifdef Z3DEBUG +#ifdef Z3DEBUG_FPA2BV_NAMES std::string p("fpa2bv"); std::string name = f->get_name().str(); @@ -326,7 +326,7 @@ void fpa2bv_converter::mk_rm_const(func_decl * f, expr_ref & result) { expr_ref bv3(m); bv3 = m.mk_fresh_const( -#ifdef Z3DEBUG +#ifdef Z3DEBUG_FPA2BV_NAMES "fpa2bv_rm" #else nullptr @@ -465,7 +465,7 @@ void fpa2bv_converter::add_core(unsigned sbits, unsigned ebits, SASSERT(is_well_sorted(m, big_d_sig)); if (ebits > sbits) - throw default_exception("there is no floating point support for division for representations with non-standard bit representations"); + throw default_exception("addition/subtract with ebits > sbits not supported"); expr_ref shifted_big(m), shifted_d_sig(m), sticky_raw(m), sticky(m); @@ -950,7 +950,7 @@ void fpa2bv_converter::mk_div(sort * s, expr_ref & rm, expr_ref & x, expr_ref & unsigned ebits = m_util.get_ebits(s); unsigned sbits = m_util.get_sbits(s); if (ebits > sbits) - throw default_exception("there is no floating point support for division for representations with non-standard bit representations"); + throw default_exception("division with ebits > sbits not supported"); SASSERT(ebits <= sbits); expr_ref a_sgn(m), a_sig(m), a_exp(m), a_lz(m), b_sgn(m), b_sig(m), b_exp(m), b_lz(m); @@ -2561,9 +2561,7 @@ void fpa2bv_converter::mk_to_fp_float(sort * to_srt, expr * rm, expr * x, expr_r res_sig = sig; res_sig = m_bv_util.mk_zero_extend(1, res_sig); // extra zero in the front for the rounder. - unsigned sig_sz = m_bv_util.get_bv_size(res_sig); - (void) sig_sz; - SASSERT(sig_sz == to_sbits + 4); + SASSERT(m_bv_util.get_bv_size(res_sig) == to_sbits + 4); expr_ref exponent_overflow(m), exponent_underflow(m); exponent_overflow = m.mk_false(); @@ -2577,7 +2575,7 @@ void fpa2bv_converter::mk_to_fp_float(sort * to_srt, expr * rm, expr * x, expr_r lz_ext = m_bv_util.mk_zero_extend(to_ebits - from_ebits + 2, lz); res_exp = m_bv_util.mk_bv_sub(res_exp, lz_ext); } - else if (from_ebits > (to_ebits + 2)) { + else if (from_ebits >= (to_ebits + 2)) { unsigned ebits_diff = from_ebits - (to_ebits + 2); // subtract lz for subnormal numbers. @@ -2617,9 +2615,6 @@ void fpa2bv_converter::mk_to_fp_float(sort * to_srt, expr * rm, expr * x, expr_r res_exp = m.mk_ite(ovf_cond, max_exp, res_exp); res_exp = m.mk_ite(udf_cond, min_exp, res_exp); } - else { // from_ebits == (to_ebits + 2) - res_exp = m_bv_util.mk_bv_sub(exp, lz); - } SASSERT(m_bv_util.get_bv_size(res_exp) == to_ebits + 2); SASSERT(is_well_sorted(m, res_exp)); @@ -3839,7 +3834,7 @@ void fpa2bv_converter::mk_rounding_mode(decl_kind k, expr_ref & result) } void fpa2bv_converter::dbg_decouple(const char * prefix, expr_ref & e) { -#ifdef Z3DEBUG +#ifdef Z3DEBUG_FPA2BV_NAMES return; // CMW: This works only for quantifier-free formulas. if (m_util.is_fp(e)) { diff --git a/src/ast/fpa/fpa2bv_converter.h b/src/ast/fpa/fpa2bv_converter.h index 3a1f2ec50..471a7c6fc 100644 --- a/src/ast/fpa/fpa2bv_converter.h +++ b/src/ast/fpa/fpa2bv_converter.h @@ -61,7 +61,7 @@ protected: public: fpa2bv_converter(ast_manager & m); - ~fpa2bv_converter(); + virtual ~fpa2bv_converter(); fpa_util & fu() { return m_util; } bv_util & bu() { return m_bv_util; } diff --git a/src/ast/fpa_decl_plugin.cpp b/src/ast/fpa_decl_plugin.cpp index 2dca61a39..5062615f2 100644 --- a/src/ast/fpa_decl_plugin.cpp +++ b/src/ast/fpa_decl_plugin.cpp @@ -207,7 +207,7 @@ sort * fpa_decl_plugin::mk_float_sort(unsigned ebits, unsigned sbits) { m_manager->raise_exception("minimum number of exponent bits is 2"); if (ebits > 63) m_manager->raise_exception("maximum number of exponent bits is 63"); - + parameter p1(ebits), p2(sbits); parameter ps[2] = { p1, p2 }; sort_size sz; @@ -929,16 +929,22 @@ bool fpa_decl_plugin::is_unique_value(app* e) const { case OP_FPA_RM_TOWARD_NEGATIVE: case OP_FPA_RM_TOWARD_ZERO: return true; - case OP_FPA_PLUS_INF: /* No; +oo == fp(#b0 #b11 #b00) */ - case OP_FPA_MINUS_INF: /* No; -oo == fp #b1 #b11 #b00) */ - case OP_FPA_PLUS_ZERO: /* No; +zero == fp #b0 #b00 #b000) */ - case OP_FPA_MINUS_ZERO: /* No; -zero == fp #b1 #b00 #b000) */ + case OP_FPA_PLUS_INF: /* No; +oo == (fp #b0 #b11 #b00) */ + case OP_FPA_MINUS_INF: /* No; -oo == (fp #b1 #b11 #b00) */ + case OP_FPA_PLUS_ZERO: /* No; +zero == (fp #b0 #b00 #b000) */ + case OP_FPA_MINUS_ZERO: /* No; -zero == (fp #b1 #b00 #b000) */ case OP_FPA_NAN: /* No; NaN == (fp #b0 #b111111 #b0000001) */ case OP_FPA_NUM: /* see NaN */ return false; - case OP_FPA_FP: - return false; /*No; generally not because of clashes with +oo, -oo, +zero, -zero, NaN */ - // a refinement would require to return true only if there is no clash with these cases. + case OP_FPA_FP: { + if (m_manager->is_value(e->get_arg(0)) && + m_manager->is_value(e->get_arg(1)) && + m_manager->is_value(e->get_arg(2))) { + bv_util bu(*m_manager); + return !bu.is_allone(e->get_arg(1)) && !bu.is_zero(e->get_arg(1)); + } + return false; + } default: return false; } diff --git a/src/ast/is_variable_test.h b/src/ast/is_variable_test.h index 461fca59d..6bf200b30 100644 --- a/src/ast/is_variable_test.h +++ b/src/ast/is_variable_test.h @@ -23,6 +23,7 @@ Revision History: class is_variable_proc { public: + virtual ~is_variable_proc() = default; virtual bool operator()(const expr* e) const = 0; }; diff --git a/src/ast/normal_forms/name_exprs.h b/src/ast/normal_forms/name_exprs.h index e744dfb02..268df8821 100644 --- a/src/ast/normal_forms/name_exprs.h +++ b/src/ast/normal_forms/name_exprs.h @@ -23,6 +23,7 @@ Notes: class expr_predicate { public: + virtual ~expr_predicate() = default; virtual bool operator()(expr * t) = 0; }; diff --git a/src/ast/num_occurs.h b/src/ast/num_occurs.h index cff4ef9ff..d8c6e6319 100644 --- a/src/ast/num_occurs.h +++ b/src/ast/num_occurs.h @@ -37,6 +37,8 @@ public: m_ignore_quantifiers(ignore_quantifiers) { } + virtual ~num_occurs() = default; + void validate(); virtual void reset() { m_num_occurs.reset(); } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index dd6f5181a..1146b7aaf 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -93,6 +93,7 @@ namespace recfun { // closure for computing whether a `rhs` expression is immediate struct is_immediate_pred { virtual bool operator()(expr * rhs) = 0; + virtual ~is_immediate_pred() = default; }; class def { diff --git a/src/ast/rewriter/array_rewriter.cpp b/src/ast/rewriter/array_rewriter.cpp index c25ae1156..88a0ee28e 100644 --- a/src/ast/rewriter/array_rewriter.cpp +++ b/src/ast/rewriter/array_rewriter.cpp @@ -703,8 +703,60 @@ br_status array_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) result = m().update_quantifier(lam, quantifier_kind::forall_k, e); return BR_REWRITE2; } - expr_ref lh1(m()), rh1(m()); + + expr_ref_vector fmls(m()); + + + auto has_large_domain = [&](sort* s, unsigned num_stores) { + unsigned sz = get_array_arity(s); + uint64_t dsz = 1; + for (unsigned i = 0; i < sz; ++i) { + sort* d = get_array_domain(s, i); + if (d->is_infinite() || d->is_very_big()) + return true; + auto const& n = d->get_num_elements(); + if (n.size() > num_stores) + return true; + dsz *= n.size(); + if (dsz > num_stores) + return true; + } + return false; + }; + + + if (m_expand_store_eq) { + expr* lhs1 = lhs; + expr* rhs1 = rhs; + unsigned num_lhs = 0, num_rhs = 0; + while (m_util.is_store(lhs1)) { + lhs1 = to_app(lhs1)->get_arg(0); + ++num_lhs; + } + while (m_util.is_store(rhs1)) { + rhs1 = to_app(rhs1)->get_arg(0); + ++num_rhs; + } + if (lhs1 == rhs1) { + mk_eq(lhs, lhs, rhs, fmls); + mk_eq(rhs, lhs, rhs, fmls); + result = m().mk_and(fmls); + return BR_REWRITE_FULL; + } + + if (m_util.is_const(lhs1, v) && m_util.is_const(rhs1, w) && + has_large_domain(lhs->get_sort(), std::max(num_lhs, num_rhs))) { + mk_eq(lhs, lhs, rhs, fmls); + mk_eq(rhs, lhs, rhs, fmls); + fmls.push_back(m().mk_eq(v, w)); + result = m().mk_and(fmls); + return BR_REWRITE_FULL; + } + } + + if (m_expand_nested_stores) { + expr_ref lh1(m()), rh1(m()); if (is_expandable_store(lhs)) { lh1 = expand_store(lhs); } @@ -719,10 +771,6 @@ br_status array_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) } } - if (!m_expand_store_eq) { - return BR_FAILED; - } - expr_ref_vector fmls(m()); #if 0 // lambda friendly version of array equality rewriting. @@ -744,46 +792,5 @@ br_status array_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) } #endif - expr* lhs1 = lhs; - unsigned num_lhs = 0, num_rhs = 0; - while (m_util.is_store(lhs1)) { - lhs1 = to_app(lhs1)->get_arg(0); - ++num_lhs; - } - expr* rhs1 = rhs; - while (m_util.is_store(rhs1)) { - rhs1 = to_app(rhs1)->get_arg(0); - ++num_rhs; - } - if (lhs1 == rhs1) { - mk_eq(lhs, lhs, rhs, fmls); - mk_eq(rhs, lhs, rhs, fmls); - result = m().mk_and(fmls); - return BR_REWRITE_FULL; - } - auto has_large_domain = [&](sort* s, unsigned num_stores) { - unsigned sz = get_array_arity(s); - uint64_t dsz = 1; - for (unsigned i = 0; i < sz; ++i) { - sort* d = get_array_domain(s, i); - if (d->is_infinite() || d->is_very_big()) - return true; - auto const& n = d->get_num_elements(); - if (n.size() > num_stores) - return true; - dsz *= n.size(); - if (dsz > num_stores) - return true; - } - return false; - }; - if (m_util.is_const(lhs1, v) && m_util.is_const(rhs1, w) && - has_large_domain(lhs->get_sort(), std::max(num_lhs, num_rhs))) { - mk_eq(lhs, lhs, rhs, fmls); - mk_eq(rhs, lhs, rhs, fmls); - fmls.push_back(m().mk_eq(v, w)); - result = m().mk_and(fmls); - return BR_REWRITE_FULL; - } return BR_FAILED; } diff --git a/src/ast/rewriter/push_app_ite.h b/src/ast/rewriter/push_app_ite.h index ae3af1222..a2e18dd25 100644 --- a/src/ast/rewriter/push_app_ite.h +++ b/src/ast/rewriter/push_app_ite.h @@ -33,6 +33,7 @@ struct push_app_ite_cfg : public default_rewriter_cfg { virtual bool is_target(func_decl * decl, unsigned num_args, expr * const * args); br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr); push_app_ite_cfg(ast_manager& m): m(m), m_conservative(true) {} + virtual ~push_app_ite_cfg() = default; void set_conservative(bool c) { m_conservative = c; } bool rewrite_patterns() const { return false; } }; diff --git a/src/ast/rewriter/recfun_rewriter.cpp b/src/ast/rewriter/recfun_rewriter.cpp index 2519d0755..b332f2256 100644 --- a/src/ast/rewriter/recfun_rewriter.cpp +++ b/src/ast/rewriter/recfun_rewriter.cpp @@ -22,10 +22,9 @@ Author: br_status recfun_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { if (m_rec.is_defined(f) && num_args > 0) { - for (unsigned i = 0; i < num_args; ++i) { + for (unsigned i = 0; i < num_args; ++i) if (!m.is_value(args[i])) return BR_FAILED; - } if (!m_rec.has_def(f)) return BR_FAILED; recfun::def const& d = m_rec.get_def(f); @@ -35,9 +34,8 @@ br_status recfun_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * result = sub(d.get_rhs(), num_args, args); return BR_REWRITE_FULL; } - else { + else return BR_FAILED; - } } diff --git a/src/ast/rewriter/seq_axioms.cpp b/src/ast/rewriter/seq_axioms.cpp index bcda71e54..557541027 100644 --- a/src/ast/rewriter/seq_axioms.cpp +++ b/src/ast/rewriter/seq_axioms.cpp @@ -850,7 +850,7 @@ namespace seq { add_clause(~eq, ge10k); for (unsigned i = 0; i < k; ++i) { - expr* ch = seq.str.mk_nth_i(ubvs, i); + expr* ch = seq.str.mk_nth_c(ubvs, i); is_digit = seq.mk_char_is_digit(ch); add_clause(~ge_len, is_digit); } @@ -1142,8 +1142,8 @@ namespace seq { /** ~contains(a, b) => ~prefix(b, a) - ~contains(a, b) => ~contains(tail(a), b) or a = empty - ~contains(a, b) & a = empty => b != empty + ~contains(a, b) => ~contains(tail(a), b) + a = empty => tail(a) = empty ~(a = empty) => a = head + tail */ void axioms::unroll_not_contains(expr* e) { @@ -1165,7 +1165,7 @@ namespace seq { expr_ref bound_tracker = m_sk.mk_length_limit(s, k); expr* s0 = nullptr; if (seq.str.is_stoi(s, s0)) - s = s0; + s = s0; add_clause(~bound_tracker, mk_le(mk_len(s), k)); return bound_tracker; } diff --git a/src/ast/rewriter/seq_eq_solver.cpp b/src/ast/rewriter/seq_eq_solver.cpp index e293743f8..ca96512f0 100644 --- a/src/ast/rewriter/seq_eq_solver.cpp +++ b/src/ast/rewriter/seq_eq_solver.cpp @@ -15,6 +15,7 @@ Author: --*/ +#include "ast/ast_pp.h" #include "ast/rewriter/seq_eq_solver.h" #include "ast/bv_decl_plugin.h" @@ -675,7 +676,7 @@ namespace seq { if (rs.size() > i) { unsigned diff = rs.size() - (i + 1); for (unsigned j = 0; same && j < i; ++j) - same = !m.are_distinct(ls[j], rs[diff + j]); + same = !m.are_distinct(ls[j], rs[diff + j]); } // ls = x ++ rs ++ y, diff = |x| else { @@ -704,8 +705,9 @@ namespace seq { bool same = true; // ls = x ++ rs' && rs = rs' ++ y, diff = |x| if (rs.size() > i) { - for (unsigned j = 1; same && j <= i; ++j) - same = !m.are_distinct(ls[diff + j], rs[j]); + for (unsigned j = 1; same && j <= i; ++j) { + same = !m.are_distinct(ls[diff + j], rs[j]); + } } // ls = x ++ rs ++ y, diff = |x| else { @@ -715,6 +717,7 @@ namespace seq { if (same) return true; } + return false; } diff --git a/src/ast/rewriter/seq_eq_solver.h b/src/ast/rewriter/seq_eq_solver.h index 6be2531b2..c6c5437b7 100644 --- a/src/ast/rewriter/seq_eq_solver.h +++ b/src/ast/rewriter/seq_eq_solver.h @@ -39,6 +39,7 @@ namespace seq { class eq_solver_context { public: + virtual ~eq_solver_context() = default; virtual void add_consequence(bool uses_dep, expr_ref_vector const& clause) = 0; virtual void add_solution(expr* var, expr* term) = 0; virtual expr* expr2rep(expr* e) = 0; diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 63a5044e5..588979534 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -859,13 +859,12 @@ br_status seq_rewriter::mk_seq_length(expr* a, expr_ref& result) { // elif offset >= len(s) then 0 // elif offset + length > len(s) then len(s) - offset // else length - expr_ref zero(m_autil.mk_int(0), m()); result = length; result = m().mk_ite(m_autil.mk_gt(m_autil.mk_add(offset, length), len_s), m_autil.mk_sub(len_s, offset), result); - result = m().mk_ite(m().mk_or(m_autil.mk_le(len_s, offset), m_autil.mk_le(length, zero), m_autil.mk_lt(offset, zero)), - zero, + result = m().mk_ite(m().mk_or(m_autil.mk_le(len_s, offset), m_autil.mk_le(length, zero()), m_autil.mk_lt(offset, zero())), + zero(), result); return BR_REWRITE_FULL; } @@ -883,52 +882,75 @@ expr_ref seq_rewriter::mk_seq_first(expr* t) { if (str().is_extract(t, s, j, k)) result = str().mk_nth_i(s, j); else - result = str().mk_nth_i(t, m_autil.mk_int(0)); + result = str().mk_nth_c(t, 0); return result; } +expr_ref seq_rewriter::mk_sub(expr* a, rational const& n) { + expr* a1, *a2; + SASSERT(n.is_int()); + rational k; + if (m_autil.is_sub(a, a1, a2) && m_autil.is_numeral(a2, k)) + return expr_ref(m_autil.mk_sub(a1, m_autil.mk_int(k + n)), m()); + if (m_autil.is_add(a, a1, a2) && m_autil.is_numeral(a2, k)) + return expr_ref(m_autil.mk_add(a1, m_autil.mk_int(k - n)), m()); + if (m_autil.is_add(a, a1, a2) && m_autil.is_numeral(a1, k)) + return expr_ref(m_autil.mk_add(a2, m_autil.mk_int(k - n)), m()); + return expr_ref(m_autil.mk_sub(a, m_autil.mk_int(n)), m()); +} + + /* * In general constructs substring(t,1,|t|-1) but if t = substring(s,j,k) then simplifies to substring(s,j+1,k-1) * This method assumes that |t| > 0. */ expr_ref seq_rewriter::mk_seq_rest(expr* t) { expr_ref result(m()); - expr* s, * j, * k; - expr_ref one(m_autil.mk_int(1), m()); - if (str().is_extract(t, s, j, k)) - result = str().mk_substr(s, m_autil.mk_add(j, one), m_autil.mk_sub(k, one)); - else - result = str().mk_substr(t, one, m_autil.mk_sub(str().mk_length(t), one)); + expr* s, * j, * k; + rational jv; + if (str().is_extract(t, s, j, k) && m_autil.is_numeral(j, jv) && jv >= 0) + result = str().mk_substr(s, m_autil.mk_int(jv + 1), mk_sub(k, 1)); + else + result = str().mk_substr(t, one(), mk_sub(str().mk_length(t), 1)); return result; } /* -* In general constructs nth(t,|t|-1) but if t = substring(s,j,k) then simplifies to nth(s,j+k-1) +* In general constructs nth(t,|t|-1) but if t = substring(s,j,|s|-j) j >= 0, then simplifies to nth(s,|s|-1) * This method assumes that |t| > 0. */ expr_ref seq_rewriter::mk_seq_last(expr* t) { expr_ref result(m()); - expr* s, * j, * k; - expr_ref one(m_autil.mk_int(1), m()); - if (str().is_extract(t, s, j, k)) - result = str().mk_nth_i(s, m_autil.mk_sub(m_autil.mk_add(j, k), one)); + expr* s, * j, * k, * s_, * len_s; + rational jv, i; + if (str().is_extract(t, s, j, k) && + m_autil.is_numeral(j, jv) && jv >= 0 && + str().is_len_sub(k, len_s, s_, i) && + s == s_ && jv == i) { + expr_ref lastpos = mk_sub(len_s, 1); + result = str().mk_nth_i(s, lastpos); + } else - result = str().mk_nth_i(t, m_autil.mk_sub(str().mk_length(t), one)); + result = str().mk_nth_i(t, m_autil.mk_sub(str().mk_length(t), one())); return result; } /* -* In general constructs substring(t,0,|t|-1) but if t = substring(s,j,k) then simplifies to substring(s,j,k-1) -* This method assumes that |t| > 0 holds. +* In general constructs substring(t,0,|t|-1) but if t = substring(s,0,k) then simplifies to substring(s,0,k-1) +* This method assumes that |t| > 0, thus, if t = substring(s,0,k) then k > 0 so substring(s,0,k-1) is correct. */ expr_ref seq_rewriter::mk_seq_butlast(expr* t) { expr_ref result(m()); expr* s, * j, * k; - expr_ref one(m_autil.mk_int(1), m()); - if (str().is_extract(t, s, j, k)) - result = str().mk_substr(s, j, m_autil.mk_sub(k, one)); + rational v; + if (str().is_extract(t, s, j, k) && m_autil.is_numeral(j, v) && v.is_zero()) { + expr_ref_vector k_min_1(m()); + k_min_1.push_back(k); + k_min_1.push_back(minus_one()); + result = str().mk_substr(s, j, m_autil.mk_add_simplify(k_min_1)); + } else - result = str().mk_substr(t, m_autil.mk_int(0), m_autil.mk_sub(str().mk_length(t), one)); + result = str().mk_substr(t, zero(), m_autil.mk_sub(str().mk_length(t), one())); return result; } @@ -1582,23 +1604,33 @@ br_status seq_rewriter::mk_seq_nth(expr* a, expr* b, expr_ref& result) { result = s; return BR_DONE; } - if (str().is_extract(a, s, p, len) && m_autil.is_numeral(p, pos1)) { + if (str().is_extract(a, s, p, len) && m_autil.is_numeral(p, pos1) && pos1 > 0) { expr_ref_vector lens(m()); rational pos2; + /* + * nth(s[k, |s| - k], b) = + * b < 0 -> nth_u(a, b) + * b + k < |s| -> nth(s, b + k) + * k >= |s| -> nth_u(empty, b) + * k < |s| <= b + k -> nth_u(a, b) + */ if (get_lengths(len, lens, pos2) && (pos1 == -pos2) && (lens.size() == 1) && (lens.get(0) == s)) { - expr_ref idx(m_autil.mk_int(pos1), m()); - idx = m_autil.mk_add(b, idx); - expr* es[2] = { s, idx }; - result = m().mk_app(m_util.get_family_id(), OP_SEQ_NTH, 2, es); + expr_ref k(m_autil.mk_int(pos1), m()); + expr_ref case1(str().mk_nth_i(s, m_autil.mk_add(b, k)), m()); + expr_ref case2(str().mk_nth_u(str().mk_empty(s->get_sort()), b), m()); + expr_ref case3(str().mk_nth_u(a, b), m()); + result = case3; + result = m().mk_ite(m_autil.mk_lt(m_autil.mk_add(k, b), str().mk_length(s)), case1, result); + result = m().mk_ite(m_autil.mk_ge(k, str().mk_length(s)), case2, result); + result = m().mk_ite(m_autil.mk_lt(b, zero()), case3, result); return BR_REWRITE_FULL; } } - expr* es[2] = { a, b}; expr* la = str().mk_length(a); result = m().mk_ite(m().mk_and(m_autil.mk_ge(b, zero()), m().mk_not(m_autil.mk_le(la, b))), - m().mk_app(m_util.get_family_id(), OP_SEQ_NTH_I, 2, es), - m().mk_app(m_util.get_family_id(), OP_SEQ_NTH_U, 2, es)); + str().mk_nth_i(a, b), + str().mk_nth_u(a, b)); return BR_REWRITE_FULL; } @@ -1678,7 +1710,7 @@ br_status seq_rewriter::mk_seq_index(expr* a, expr* b, expr* c, expr_ref& result return BR_DONE; } if (m_autil.is_numeral(c, r) && r.is_neg()) { - result = m_autil.mk_int(-1); + result = minus_one(); return BR_DONE; } @@ -1688,10 +1720,10 @@ br_status seq_rewriter::mk_seq_index(expr* a, expr* b, expr* c, expr_ref& result } if (str().is_empty(b)) { - result = m().mk_ite(m().mk_and(m_autil.mk_le(m_autil.mk_int(0), c), + result = m().mk_ite(m().mk_and(m_autil.mk_le(zero(), c), m_autil.mk_le(c, str().mk_length(a))), c, - m_autil.mk_int(-1)); + minus_one()); return BR_REWRITE2; } @@ -2307,7 +2339,7 @@ br_status seq_rewriter::mk_str_to_code(expr* a, expr_ref& result) { if (s.length() == 1) result = m_autil.mk_int(s[0]); else - result = m_autil.mk_int(-1); + result = minus_one(); return BR_DONE; } return BR_FAILED; @@ -2448,7 +2480,7 @@ br_status seq_rewriter::mk_str_stoi(expr* a, expr_ref& result) { result = m_autil.mk_int(ch - '0'); } else { - result = m_autil.mk_int(-1); + result = minus_one(); } return BR_DONE; } @@ -2456,7 +2488,7 @@ br_status seq_rewriter::mk_str_stoi(expr* a, expr_ref& result) { expr_ref_vector as(m()); str().get_concat_units(a, as); if (as.empty()) { - result = m_autil.mk_int(-1); + result = minus_one(); return BR_DONE; } if (str().is_unit(as.back())) { @@ -2466,11 +2498,11 @@ br_status seq_rewriter::mk_str_stoi(expr* a, expr_ref& result) { expr_ref tail(str().mk_stoi(as.back()), m()); expr_ref head(str().mk_concat(as.size() - 1, as.data(), a->get_sort()), m()); expr_ref stoi_head(str().mk_stoi(head), m()); - result = m().mk_ite(m_autil.mk_ge(stoi_head, m_autil.mk_int(0)), + result = m().mk_ite(m_autil.mk_ge(stoi_head, zero()), m_autil.mk_add(m_autil.mk_mul(m_autil.mk_int(10), stoi_head), tail), - m_autil.mk_int(-1)); + minus_one()); - result = m().mk_ite(m_autil.mk_ge(tail, m_autil.mk_int(0)), + result = m().mk_ite(m_autil.mk_ge(tail, zero()), result, tail); result = m().mk_ite(str().mk_is_empty(head), @@ -2481,7 +2513,7 @@ br_status seq_rewriter::mk_str_stoi(expr* a, expr_ref& result) { if (str().is_unit(as.get(0), u) && m_util.is_const_char(u, ch) && '0' == ch) { result = str().mk_concat(as.size() - 1, as.data() + 1, as[0]->get_sort()); result = m().mk_ite(str().mk_is_empty(result), - m_autil.mk_int(0), + zero(), str().mk_stoi(result)); return BR_REWRITE_FULL; } @@ -2573,7 +2605,7 @@ bool seq_rewriter::is_sequence(expr* e, expr_ref_vector& seq) { } /* - s = head + tail where |head| = 1 + s = [head] + tail where head is the first element of s */ bool seq_rewriter::get_head_tail(expr* s, expr_ref& head, expr_ref& tail) { expr* h = nullptr, *t = nullptr; @@ -2670,10 +2702,10 @@ expr_ref seq_rewriter::re_predicate(expr* cond, sort* seq_sort) { expr_ref seq_rewriter::is_nullable(expr* r) { STRACE("seq_verbose", tout << "is_nullable: " << mk_pp(r, m()) << std::endl;); - expr_ref result(m_op_cache.find(_OP_RE_IS_NULLABLE, r, nullptr), m()); + expr_ref result(m_op_cache.find(_OP_RE_IS_NULLABLE, r, nullptr, nullptr), m()); if (!result) { result = is_nullable_rec(r); - m_op_cache.insert(_OP_RE_IS_NULLABLE, r, nullptr, result); + m_op_cache.insert(_OP_RE_IS_NULLABLE, r, nullptr, nullptr, result); } STRACE("seq_verbose", tout << "is_nullable result: " << result << std::endl;); @@ -2691,7 +2723,7 @@ expr_ref seq_rewriter::is_nullable_rec(expr* r) { re().is_intersection(r, r1, r2)) { m_br.mk_and(is_nullable(r1), is_nullable(r2), result); } - else if (re().is_union(r, r1, r2)) { + else if (re().is_union(r, r1, r2) || re().is_antimorov_union(r, r1, r2)) { m_br.mk_or(is_nullable(r1), is_nullable(r2), result); } else if (re().is_diff(r, r1, r2)) { @@ -2701,6 +2733,7 @@ expr_ref seq_rewriter::is_nullable_rec(expr* r) { else if (re().is_star(r) || re().is_opt(r) || re().is_full_seq(r) || + re().is_epsilon(r) || (re().is_loop(r, r1, lo) && lo == 0) || (re().is_loop(r, r1, lo, hi) && lo == 0)) { result = m().mk_true(); @@ -2724,7 +2757,7 @@ expr_ref seq_rewriter::is_nullable_rec(expr* r) { result = is_nullable(r1); } else if (m().is_ite(r, cond, r1, r2)) { - result = m().mk_ite(cond, is_nullable(r1), is_nullable(r2)); + m_br.mk_ite(cond, is_nullable(r1), is_nullable(r2), result); } else if (m_util.is_re(r, seq_sort)) { result = is_nullable_symbolic_regex(r, seq_sort); @@ -2881,7 +2914,8 @@ br_status seq_rewriter::mk_re_reverse(expr* r, expr_ref& result) { br_status seq_rewriter::mk_re_derivative(expr* ele, expr* r, expr_ref& result) { result = mk_derivative(ele, r); // TBD: we may even declare BR_DONE here and potentially miss some simplifications - return re().is_derivative(result) ? BR_DONE : BR_REWRITE_FULL; + // return re().is_derivative(result) ? BR_DONE : BR_REWRITE_FULL; + return BR_DONE; } /* @@ -2976,29 +3010,406 @@ bool seq_rewriter::check_deriv_normal_form(expr* r, int level) { } #endif -/* - Memoized, recursive implementation of the symbolic derivative such that - the result is in normal form. +expr_ref seq_rewriter::mk_derivative(expr* r) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref v(m().mk_var(0, ele_sort), m()); + return mk_antimirov_deriv(v, r, m().mk_true()); +} - Functions without _rec are memoized wrappers, which call the _rec - version if lookup fails. - - The main logic is in mk_der_op_rec for combining normal forms. -*/ expr_ref seq_rewriter::mk_derivative(expr* ele, expr* r) { - STRACE("seq_verbose", tout << "derivative: " << mk_pp(ele, m()) - << "," << mk_pp(r, m()) << std::endl;); - expr_ref result(m_op_cache.find(OP_RE_DERIVATIVE, ele, r), m()); + return mk_antimirov_deriv(ele, r, m().mk_true()); +} + +expr_ref seq_rewriter::mk_antimirov_deriv(expr* e, expr* r, expr* path) { + // Ensure references are owned + expr_ref _e(e, m()), _path(path, m()), _r(r, m()); + expr_ref result(m_op_cache.find(OP_RE_DERIVATIVE, e, r, path), m()); if (!result) { - result = mk_derivative_rec(ele, r); - m_op_cache.insert(OP_RE_DERIVATIVE, ele, r, result); + mk_antimirov_deriv_rec(e, r, path, result); + m_op_cache.insert(OP_RE_DERIVATIVE, e, r, path, result); + STRACE("seq_regex", tout << "D(" << mk_pp(e, m()) << "," << mk_pp(r, m()) << "," << mk_pp(path, m()) << ")" << std::endl;); + STRACE("seq_regex", tout << "= " << mk_pp(result, m()) << std::endl;); } - STRACE("seq_verbose", tout << "derivative result: " - << mk_pp(result, m()) << std::endl;); - CASSERT("seq_regex", check_deriv_normal_form(r)); return result; } +void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref& result) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + SASSERT(ele_sort == e->get_sort()); + expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; + expr_ref c1(m()); + expr_ref c2(m()); + auto nothing = [&]() { return expr_ref(re().mk_empty(r->get_sort()), m()); }; + auto epsilon = [&]() { return expr_ref(re().mk_epsilon(seq_sort), m()); }; + auto dotstar = [&]() { return expr_ref(re().mk_full_seq(r->get_sort()), m()); }; + unsigned lo = 0, hi = 0; + if (re().is_empty(r) || re().is_epsilon(r)) + // D(e,[]) = D(e,()) = [] + result = nothing(); + else if (re().is_full_seq(r) || re().is_dot_plus(r)) + // D(e,.*) = D(e,.+) = .* + result = dotstar(); + else if (re().is_full_char(r)) + // D(e,.) = () + result = epsilon(); + else if (re().is_to_re(r, r1)) { + expr_ref h(m()); + expr_ref t(m()); + // here r1 is a sequence + if (get_head_tail(r1, h, t)) { + if (eq_char(e, h)) + result = re().mk_to_re(t); + else if (neq_char(e, h)) + result = nothing(); + else + result = re().mk_ite_simplify(m().mk_eq(e, h), re().mk_to_re(t), nothing()); + } + else { + // observe that the precondition |r1|>0 is is implied by c1 for use of mk_seq_first + m_br.mk_and(m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))), m().mk_eq(mk_seq_first(r1), e), c1); + m_br.mk_and(path, c1, c2); + if (m().is_false(c2)) + result = nothing(); + else + // observe that the precondition |r1|>0 is implied by c1 for use of mk_seq_rest + result = m().mk_ite(c1, re().mk_to_re(mk_seq_rest(r1)), nothing()); + } + } + else if (re().is_reverse(r, r2)) + if (re().is_to_re(r2, r1)) { + // here r1 is a sequence + // observe that the precondition |r1|>0 of mk_seq_last is implied by c1 + m_br.mk_and(m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))), m().mk_eq(mk_seq_last(r1), e), c1); + m_br.mk_and(path, c1, c2); + if (m().is_false(c2)) + result = nothing(); + else + // observe that the precondition |r1|>0 of mk_seq_rest is implied by c1 + result = re().mk_ite_simplify(c1, re().mk_reverse(re().mk_to_re(mk_seq_butlast(r1))), nothing()); + } + else { + result = mk_regex_reverse(r2); + if (result.get() == r) + //r2 is an uninterpreted regex that is stuck + //for example if r = (re.reverse R) where R is a regex variable then + //here result.get() == r + result = re().mk_derivative(e, result); + else + result = mk_antimirov_deriv(e, result, path); + } + else if (re().is_concat(r, r1, r2)) { + expr_ref r1nullable(is_nullable(r1), m()); + c1 = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), r2); + expr_ref r1nullable_and_path(m()); + m_br.mk_and(r1nullable, path, r1nullable_and_path); + if (m().is_false(r1nullable_and_path)) + // D(e,r1)r2 + result = c1; + else + // D(e,r1)r2|(ite (r1nullable) (D(e,r2)) []) + // observe that (mk_ite_simplify(true, D(e,r2), []) = D(e,r2) + result = mk_antimirov_deriv_union(c1, re().mk_ite_simplify(r1nullable, mk_antimirov_deriv(e, r2, path), nothing())); + } + else if (m().is_ite(r, c, r1, r2)) { + c1 = simplify_path(m().mk_and(c, path)); + c2 = simplify_path(m().mk_and(m().mk_not(c), path)); + if (m().is_false(c1)) + result = mk_antimirov_deriv(e, r2, c2); + else if (m().is_false(c2)) + result = mk_antimirov_deriv(e, r1, c1); + else + result = re().mk_ite_simplify(c, mk_antimirov_deriv(e, r1, c1), mk_antimirov_deriv(e, r2, c2)); + } + else if (re().is_range(r, r1, r2)) { + expr_ref range(m()); + expr_ref psi(m().mk_false(), m()); + if (str().is_unit_string(r1, c1) && str().is_unit_string(r2, c2)) { + SASSERT(u().is_char(c1)); + SASSERT(u().is_char(c2)); + // case: c1 <= e <= c2 + range = simplify_path(m().mk_and(u().mk_le(c1, e), u().mk_le(e, c2))); + psi = simplify_path(m().mk_and(path, range)); + } + else if (!str().is_string(r1) && str().is_unit_string(r2, c2)) { + SASSERT(u().is_char(c2)); + // r1 nonground: |r1|=1 & r1[0] <= e <= c2 + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r1_length_eq_one(m().mk_eq(str().mk_length(r1), one), m()); + expr_ref r1_0(str().mk_nth_i(r1, zero), m()); + range = simplify_path(m().mk_and(r1_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, c2)))); + psi = simplify_path(m().mk_and(path, range)); + } + else if (!str().is_string(r2) && str().is_unit_string(r1, c1)) { + SASSERT(u().is_char(c1)); + // r2 nonground: |r2|=1 & c1 <= e <= r2_0 + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); + expr_ref r2_0(str().mk_nth_i(r2, zero), m()); + range = simplify_path(m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(c1, e), u().mk_le(e, r2_0)))); + psi = simplify_path(m().mk_and(path, range)); + } + else if (!str().is_string(r1) && !str().is_string(r2)) { + // both r1 and r2 nonground: |r1|=1 & |r2|=1 & r1[0] <= e <= r2[0] + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r1_length_eq_one(m().mk_eq(str().mk_length(r1), one), m()); + expr_ref r1_0(str().mk_nth_i(r1, zero), m()); + expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); + expr_ref r2_0(str().mk_nth_i(r2, zero), m()); + range = simplify_path(m().mk_and(r1_length_eq_one, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, r2_0))))); + psi = simplify_path(m().mk_and(path, range)); + } + if (m().is_false(psi)) + result = nothing(); + else + result = re().mk_ite_simplify(range, epsilon(), nothing()); + } + else if (re().is_union(r, r1, r2)) + result = mk_antimirov_deriv_union(mk_antimirov_deriv(e, r1, path), mk_antimirov_deriv(e, r2, path)); + else if (re().is_intersection(r, r1, r2)) + result = mk_antimirov_deriv_intersection( + mk_antimirov_deriv(e, r1, path), + mk_antimirov_deriv(e, r2, path), m().mk_true()); + else if (re().is_star(r, r1) || re().is_plus(r, r1) || (re().is_loop(r, r1, lo) && 0 <= lo && lo <= 1)) + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_star(r1)); + else if (re().is_loop(r, r1, lo)) + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_loop(r1, lo - 1)); + else if (re().is_loop(r, r1, lo, hi)) { + if ((lo == 0 && hi == 0) || hi < lo) + result = nothing(); + else + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_loop(r1, (lo == 0 ? 0 : lo - 1), hi - 1)); + } + else if (re().is_opt(r, r1)) + result = mk_antimirov_deriv(e, r1, path); + else if (re().is_complement(r, r1)) + // D(e,~r1) = ~D(e,r1) + result = mk_antimirov_deriv_negate(mk_antimirov_deriv(e, r1, path)); + else if (re().is_diff(r, r1, r2)) + result = mk_antimirov_deriv_intersection( + mk_antimirov_deriv(e, r1, path), + mk_antimirov_deriv_negate(mk_antimirov_deriv(e, r2, path)), m().mk_true()); + else if (re().is_of_pred(r, r1)) { + array_util array(m()); + expr* args[2] = { r1, e }; + result = array.mk_select(2, args); + // Use mk_der_cond to normalize + result = mk_der_cond(result, e, seq_sort); + } + else + // stuck cases + result = re().mk_derivative(e, r); +} + +expr_ref seq_rewriter::mk_antimirov_deriv_intersection(expr* d1, expr* d2, expr* path) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(d1, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref result(m()); + expr* c, * a, * b; + if (d1 == d2 || re().is_full_seq(d2) || re().is_empty(d1)) + result = d1; + else if (re().is_full_seq(d1) || re().is_empty(d2)) + result = d2; + else if (m().is_ite(d1, c, a, b)) { + expr_ref path_and_c(simplify_path(m().mk_and(path, c)), m()); + expr_ref path_and_notc(simplify_path(m().mk_and(path, m().mk_not(c))), m()); + if (m().is_false(path_and_c)) + result = mk_antimirov_deriv_intersection(b, d2, path); + else if (m().is_false(path_and_notc)) + result = mk_antimirov_deriv_intersection(a, d2, path); + else + result = m().mk_ite(c, mk_antimirov_deriv_intersection(a, d2, path_and_c), + mk_antimirov_deriv_intersection(b, d2, path_and_notc)); + } + else if (m().is_ite(d2)) + // swap d1 and d2 + result = mk_antimirov_deriv_intersection(d2, d1, path); + else if (re().is_union(d1, a, b)) + // distribute intersection over the union in d1 + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(a, d2, path), mk_antimirov_deriv_intersection(b, d2, path)); + else if (re().is_union(d2, a, b)) + // distribute intersection over the union in d2 + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(d1, a, path), mk_antimirov_deriv_intersection(d1, b, path)); + else + // in all other cases create the intersection regex + // TODO: flatten, order and merge d1 and d2 to maintain equality under similarity + result = (d1->get_id() <= d2->get_id() ? re().mk_inter(d1, d2) : re().mk_inter(d2, d1)); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_concat(expr* d, expr* r) { + expr_ref result(m()); + // Take reference count of r and d + expr_ref _r(r, m()), _d(d, m()); + expr* c, * t, * e; + if (m().is_ite(d, c, t, e)) + result = m().mk_ite(c, mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); + else if (re().is_union(d, t, e)) + result = re().mk_union(mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); + else + result = mk_re_append(d, r); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* d) { + sort* seq_sort = nullptr; + VERIFY(m_util.is_re(d, seq_sort)); + auto nothing = [&]() { return expr_ref(re().mk_empty(d->get_sort()), m()); }; + auto epsilon = [&]() { return expr_ref(re().mk_epsilon(seq_sort), m()); }; + auto dotstar = [&]() { return expr_ref(re().mk_full_seq(d->get_sort()), m()); }; + auto dotplus = [&]() { return expr_ref(re().mk_plus(re().mk_full_char(d->get_sort())), m()); }; + expr_ref result(m()); + expr* c, * t, * e; + if (re().is_empty(d)) + result = dotstar(); + else if (re().is_epsilon(d)) + result = dotplus(); + else if (re().is_full_seq(d)) + result = nothing(); + else if (re().is_dot_plus(d)) + result = epsilon(); + else if (m().is_ite(d, c, t, e)) + result = m().mk_ite(c, mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + else if (re().is_union(d, t, e)) + result = re().mk_inter(mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + else if (re().is_intersection(d, t, e)) + result = re().mk_union(mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + else if (re().is_complement(d, t)) + result = t; + else + result = re().mk_complement(d); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_union(expr* d1, expr* d2) { + expr_ref result(m()); + if (re().is_empty(d1) || re().is_full_seq(d2)) + result = d2; + else if (re().is_empty(d2) || re().is_full_seq(d1)) + result = d1; + else if (re().is_dot_plus(d1) && re().get_info(d2).min_length > 0) + result = d1; + else if (re().is_dot_plus(d2) && re().get_info(d1).min_length > 0) + result = d2; + else + // TODO: flatten, order and merge d1 and d2 to maintain equality under similarity + result = (d1->get_id() <= d2->get_id() ? re().mk_union(d1, d2) : re().mk_union(d2, d1)); + return result; +} + +expr_ref seq_rewriter::mk_regex_reverse(expr* r) { + expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; + unsigned lo = 0, hi = 0; + expr_ref result(m()); + if (re().is_empty(r) || re().is_range(r) || re().is_epsilon(r) || re().is_full_seq(r) || + re().is_full_char(r) || re().is_dot_plus(r) || re().is_of_pred(r)) + result = r; + else if (re().is_to_re(r)) + result = re().mk_reverse(r); + else if (re().is_reverse(r, r1)) + result = r1; + else if (re().is_concat(r, r1, r2)) + result = mk_regex_concat(mk_regex_reverse(r2), mk_regex_reverse(r1)); + else if (m().is_ite(r, c, r1, r2)) + result = m().mk_ite(c, mk_regex_reverse(r1), mk_regex_reverse(r2)); + else if (re().is_union(r, r1, r2)) + result = re().mk_union(mk_regex_reverse(r1), mk_regex_reverse(r2)); + else if (re().is_intersection(r, r1, r2)) + result = re().mk_inter(mk_regex_reverse(r1), mk_regex_reverse(r2)); + else if (re().is_diff(r, r1, r2)) + result = re().mk_diff(mk_regex_reverse(r1), mk_regex_reverse(r2)); + else if (re().is_star(r, r1)) + result = re().mk_star(mk_regex_reverse(r1)); + else if (re().is_plus(r, r1)) + result = re().mk_plus(mk_regex_reverse(r1)); + else if (re().is_loop(r, r1, lo)) + result = re().mk_loop(mk_regex_reverse(r1), lo); + else if (re().is_loop(r, r1, lo, hi)) + result = re().mk_loop(mk_regex_reverse(r1), lo, hi); + else if (re().is_opt(r, r1)) + result = re().mk_opt(mk_regex_reverse(r1)); + else if (re().is_complement(r, r1)) + result = re().mk_complement(mk_regex_reverse(r1)); + else + //stuck cases: such as r being a regex variable + //observe that re().mk_reverse(to_re(s)) is not a stuck case + result = re().mk_reverse(r); + return result; +} + +expr_ref seq_rewriter::mk_regex_concat(expr* r, expr* s) { + sort* seq_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + SASSERT(r->get_sort() == s->get_sort()); + expr_ref result(m()); + expr* r1, * r2; + if (re().is_epsilon(r) || re().is_empty(s)) + result = s; + else if (re().is_epsilon(s) || re().is_empty(r)) + result = r; + else if (re().is_full_seq(r) && re().is_full_seq(s)) + result = r; + else if (re().is_concat(r, r1, r2)) + //create the resulting concatenation in right-associative form + result = mk_regex_concat(r1, mk_regex_concat(r2, s)); + else { + //TODO: perhaps simplifiy some further cases such as .*. = ..* = .*.+ = .+.* = .+ + result = re().mk_concat(r, s); + } + return result; +} + +expr_ref seq_rewriter::mk_in_antimirov(expr* s, expr* d){ + expr_ref result(mk_in_antimirov_rec(s, d), m()); + return result; +} + +expr_ref seq_rewriter::mk_in_antimirov_rec(expr* s, expr* d) { + expr* c, * d1, * d2; + expr_ref result(m()); + if (re().is_full_seq(d) || (str().min_length(s) > 0 && re().is_dot_plus(d))) + // s in .* <==> true, also: s in .+ <==> true when |s|>0 + result = m().mk_true(); + else if (re().is_empty(d) || (str().min_length(s) > 0 && re().is_epsilon(d))) + // s in [] <==> false, also: s in () <==> false when |s|>0 + result = m().mk_false(); + else if (m().is_ite(d, c, d1, d2)) + result = re().mk_ite_simplify(c, mk_in_antimirov_rec(s, d1), mk_in_antimirov_rec(s, d2)); + else if (re().is_union(d, d1, d2)) + m_br.mk_or(mk_in_antimirov_rec(s, d1), mk_in_antimirov_rec(s, d2), result); + else + result = re().mk_in_re(s, d); + return result; +} + +/* +path is typically a conjunction of (negated) character equations or constraints that can potentially be simplified +the first element of each equation is assumed to be the element parameter, for example x = (:var 0), +for example a constraint x='a' & x='b' is simplified to false +*/ +expr_ref seq_rewriter::simplify_path(expr* path) { + //TODO: more systematic simplifications + expr_ref result(path, m()); + expr* h = nullptr, * t = nullptr, * lhs = nullptr, * rhs = nullptr, * h1 = nullptr; + if (m().is_and(path, h, t)) { + if (m().is_true(h)) + result = simplify_path(t); + else if (m().is_true(t)) + result = simplify_path(h); + else if (m().is_eq(h, lhs, rhs) || (m().is_not(h, h1) && m().is_eq(h1, lhs, rhs))) + elim_condition(lhs, result); + } + return result; +} + + expr_ref seq_rewriter::mk_der_antimorov_union(expr* r1, expr* r2) { return mk_der_op(_OP_RE_ANTIMOROV_UNION, r1, r2); } @@ -3016,7 +3427,7 @@ expr_ref seq_rewriter::mk_der_concat(expr* r1, expr* r2) { } /* - Utility functions to decide char <, ==, and <=. + Utility functions to decide char <, ==, !=, and <=. Return true if deduced, false if unknown. */ bool seq_rewriter::lt_char(expr* ch1, expr* ch2) { @@ -3027,6 +3438,11 @@ bool seq_rewriter::lt_char(expr* ch1, expr* ch2) { bool seq_rewriter::eq_char(expr* ch1, expr* ch2) { return ch1 == ch2; } +bool seq_rewriter::neq_char(expr* ch1, expr* ch2) { + unsigned u1, u2; + return u().is_const_char(ch1, u1) && + u().is_const_char(ch2, u2) && (u1 != u2); +} bool seq_rewriter::le_char(expr* ch1, expr* ch2) { return eq_char(ch1, ch2) || lt_char(ch1, ch2); } @@ -3257,10 +3673,10 @@ expr_ref seq_rewriter::mk_der_op(decl_kind k, expr* a, expr* b) { default: break; } - result = m_op_cache.find(k, a, b); + result = m_op_cache.find(k, a, b, nullptr); if (!result) { result = mk_der_op_rec(k, a, b); - m_op_cache.insert(k, a, b, result); + m_op_cache.insert(k, a, b, nullptr, result); } CASSERT("seq_regex", check_deriv_normal_form(result)); return result; @@ -3269,7 +3685,7 @@ expr_ref seq_rewriter::mk_der_op(decl_kind k, expr* a, expr* b) { expr_ref seq_rewriter::mk_der_compl(expr* r) { STRACE("seq_verbose", tout << "mk_der_compl: " << mk_pp(r, m()) << std::endl;); - expr_ref result(m_op_cache.find(OP_RE_COMPLEMENT, r, nullptr), m()); + expr_ref result(m_op_cache.find(OP_RE_COMPLEMENT, r, nullptr, nullptr), m()); if (!result) { expr* c = nullptr, * r1 = nullptr, * r2 = nullptr; if (re().is_antimorov_union(r, r1, r2)) { @@ -3285,7 +3701,7 @@ expr_ref seq_rewriter::mk_der_compl(expr* r) { } else if (BR_FAILED == mk_re_complement(r, result)) result = re().mk_complement(r); - m_op_cache.insert(OP_RE_COMPLEMENT, r, nullptr, result); + m_op_cache.insert(OP_RE_COMPLEMENT, r, nullptr, nullptr, result); } CASSERT("seq_regex", check_deriv_normal_form(result)); return result; @@ -3509,7 +3925,7 @@ expr_ref seq_rewriter::mk_derivative_rec(expr* ele, expr* r) { // construct the term (if (r2 != () and (ele = (last r2)) then reverse(to_re (butlast r2)) else [])) // hd = first of reverse(r2) i.e. last of r2 // tl = rest of reverse(r2) i.e. butlast of r2 - //hd = str().mk_nth_i(r2, m_autil.mk_sub(str().mk_length(r2), m_autil.mk_int(1))); + //hd = str().mk_nth_i(r2, m_autil.mk_sub(str().mk_length(r2), one())); hd = mk_seq_last(r2); m_br.mk_and(m().mk_not(m().mk_eq(r2, str().mk_empty(seq_sort))), m().mk_eq(hd, ele), result); tl = re().mk_to_re(mk_seq_butlast(r2)); @@ -3537,9 +3953,9 @@ expr_ref seq_rewriter::mk_derivative_rec(expr* ele, expr* r) { return mk_empty(); } } - expr* e1 = nullptr, *e2 = nullptr; + expr* e1 = nullptr, * e2 = nullptr; if (str().is_unit(r1, e1) && str().is_unit(r2, e2)) { - SASSERT(u().is_char(e1)); + SASSERT(u().is_char(e1)); // Use mk_der_cond to normalize STRACE("seq_verbose", tout << "deriv range str" << std::endl;); expr_ref p1(u().mk_le(e1, ele), m()); @@ -3760,7 +4176,7 @@ br_status seq_rewriter::mk_str_in_regexp(expr* a, expr* b, expr_ref& result) { (re().is_union(b, b1, eps) && re().is_epsilon(eps)) || (re().is_union(b, eps, b1) && re().is_epsilon(eps))) { - result = m().mk_ite(m().mk_eq(str().mk_length(a), m_autil.mk_int(0)), + result = m().mk_ite(m().mk_eq(str().mk_length(a), zero()), m().mk_true(), re().mk_in_re(a, b1)); return BR_REWRITE_FULL; @@ -3775,8 +4191,10 @@ br_status seq_rewriter::mk_str_in_regexp(expr* a, expr* b, expr_ref& result) { expr_ref hd(m()), tl(m()); if (get_head_tail(a, hd, tl)) { - result = re().mk_in_re(tl, re().mk_derivative(hd, b)); - return BR_REWRITE2; + //result = re().mk_in_re(tl, re().mk_derivative(hd, b)); + //result = re().mk_in_re(tl, mk_derivative(hd, b)); + result = mk_in_antimirov(tl, mk_antimirov_deriv(hd, b, m().mk_true())); + return BR_REWRITE_FULL; } if (get_head_tail_reversed(a, hd, tl)) { @@ -3791,7 +4209,7 @@ br_status seq_rewriter::mk_str_in_regexp(expr* a, expr* b, expr_ref& result) { expr_ref len_a(str().mk_length(a), m()); expr_ref len_tl(m_autil.mk_sub(len_a, len_hd), m()); result = m().mk_and(m_autil.mk_ge(len_a, len_hd), - re().mk_in_re(str().mk_substr(a, m_autil.mk_int(0), len_hd), hd), + re().mk_in_re(str().mk_substr(a, zero(), len_hd), hd), re().mk_in_re(str().mk_substr(a, len_hd, len_tl), tl)); return BR_REWRITE_FULL; } @@ -3802,7 +4220,7 @@ br_status seq_rewriter::mk_str_in_regexp(expr* a, expr* b, expr_ref& result) { expr_ref len_hd(m_autil.mk_sub(len_a, len_tl), m()); expr* s = nullptr; result = m().mk_and(m_autil.mk_ge(len_a, len_tl), - re().mk_in_re(str().mk_substr(a, m_autil.mk_int(0), len_hd), hd), + re().mk_in_re(str().mk_substr(a, zero(), len_hd), hd), (re().is_to_re(tl, s) ? m().mk_eq(s, str().mk_substr(a, len_hd, len_tl)) : re().mk_in_re(str().mk_substr(a, len_hd, len_tl), tl))); return BR_REWRITE_FULL; @@ -3912,6 +4330,10 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { return BR_REWRITE2; } expr* a1 = nullptr, *b1 = nullptr; + if (re().is_to_re(a, a1) && re().is_to_re(b, b1)) { + result = re().mk_to_re(str().mk_concat(a1, b1)); + return BR_DONE; + } if (re().is_star(a, a1) && re().is_star(b, b1) && a1 == b1) { result = a; return BR_DONE; @@ -5151,15 +5573,15 @@ bool seq_rewriter::reduce_eq_empty(expr* l, expr* r, expr_ref& result) { if (str().is_extract(r, s, offset, len)) { expr_ref len_s(str().mk_length(s), m()); expr_ref_vector fmls(m()); - fmls.push_back(m_autil.mk_lt(offset, m_autil.mk_int(0))); + fmls.push_back(m_autil.mk_lt(offset, zero())); fmls.push_back(m().mk_eq(s, l)); - fmls.push_back(m_autil.mk_le(len, m_autil.mk_int(0))); + fmls.push_back(m_autil.mk_le(len, zero())); fmls.push_back(m_autil.mk_le(len_s, offset)); result = m().mk_or(fmls); return true; } if (str().is_itos(r, s)) { - result = m_autil.mk_lt(s, m_autil.mk_int(0)); + result = m_autil.mk_lt(s, zero()); return true; } return false; @@ -5275,19 +5697,20 @@ seq_rewriter::op_cache::op_cache(ast_manager& m): m_trail(m) {} -expr* seq_rewriter::op_cache::find(decl_kind op, expr* a, expr* b) { - op_entry e(op, a, b, nullptr); +expr* seq_rewriter::op_cache::find(decl_kind op, expr* a, expr* b, expr* c) { + op_entry e(op, a, b, c, nullptr); m_table.find(e, e); return e.r; } -void seq_rewriter::op_cache::insert(decl_kind op, expr* a, expr* b, expr* r) { +void seq_rewriter::op_cache::insert(decl_kind op, expr* a, expr* b, expr* c, expr* r) { cleanup(); if (a) m_trail.push_back(a); if (b) m_trail.push_back(b); + if (c) m_trail.push_back(c); if (r) m_trail.push_back(r); - m_table.insert(op_entry(op, a, b, r)); + m_table.insert(op_entry(op, a, b, c, r)); } void seq_rewriter::op_cache::cleanup() { diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index f726a4673..20978c279 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -117,20 +117,20 @@ class seq_rewriter { class op_cache { struct op_entry { decl_kind k; - expr* a, *b, *r; - op_entry(decl_kind k, expr* a, expr* b, expr* r): k(k), a(a), b(b), r(r) {} - op_entry():k(0), a(nullptr), b(nullptr), r(nullptr) {} + expr* a, *b, *c, *r; + op_entry(decl_kind k, expr* a, expr* b, expr* c, expr* r): k(k), a(a), b(b), c(c), r(r) {} + op_entry():k(0), a(nullptr), b(nullptr), c(nullptr), r(nullptr) {} }; struct hash_entry { unsigned operator()(op_entry const& e) const { - return mk_mix(e.k, e.a ? e.a->get_id() : 0, e.b ? e.b->get_id() : 0); + return combine_hash(mk_mix(e.k, e.a ? e.a->get_id() : 0, e.b ? e.b->get_id() : 0), e.c ? e.c->get_id() : 0); } }; struct eq_entry { - bool operator()(op_entry const& a, op_entry const& b) const { - return a.k == b.k && a.a == b.a && a.b == b.b; + bool operator()(op_entry const& a, op_entry const& b) const { + return a.k == b.k && a.a == b.a && a.b == b.b && a.c == b.c; } }; @@ -143,8 +143,8 @@ class seq_rewriter { public: op_cache(ast_manager& m); - expr* find(decl_kind op, expr* a, expr* b); - void insert(decl_kind op, expr* a, expr* b, expr* r); + expr* find(decl_kind op, expr* a, expr* b, expr* c); + void insert(decl_kind op, expr* a, expr* b, expr* c, expr* r); }; seq_util m_util; @@ -208,8 +208,24 @@ class seq_rewriter { bool check_deriv_normal_form(expr* r, int level = 3); #endif + void mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref& result); + + expr_ref mk_antimirov_deriv(expr* e, expr* r, expr* path); + expr_ref mk_in_antimirov_rec(expr* s, expr* d); + expr_ref mk_in_antimirov(expr* s, expr* d); + + expr_ref mk_antimirov_deriv_intersection(expr* d1, expr* d2, expr* path); + expr_ref mk_antimirov_deriv_concat(expr* d, expr* r); + expr_ref mk_antimirov_deriv_negate(expr* d); + expr_ref mk_antimirov_deriv_union(expr* d1, expr* d2); + expr_ref mk_regex_reverse(expr* r); + expr_ref mk_regex_concat(expr* r1, expr* r2); + + expr_ref simplify_path(expr* path); + bool lt_char(expr* ch1, expr* ch2); bool eq_char(expr* ch1, expr* ch2); + bool neq_char(expr* ch1, expr* ch2); bool le_char(expr* ch1, expr* ch2); bool pred_implies(expr* a, expr* b); bool are_complements(expr* r1, expr* r2) const; @@ -286,6 +302,8 @@ class seq_rewriter { expr_ref zero() { return expr_ref(m_autil.mk_int(0), m()); } expr_ref one() { return expr_ref(m_autil.mk_int(1), m()); } expr_ref minus_one() { return expr_ref(m_autil.mk_int(-1), m()); } + expr_ref mk_sub(expr* a, rational const& n); + expr_ref mk_sub(expr* a, unsigned n) { return mk_sub(a, rational(n)); } bool is_suffix(expr* s, expr* offset, expr* len); bool is_prefix(expr* s, expr* offset, expr* len); @@ -379,9 +397,19 @@ public: void add_seqs(expr_ref_vector const& ls, expr_ref_vector const& rs, expr_ref_pair_vector& new_eqs); - // Expose derivative and nullability check + /* + create the nullability check for r + */ expr_ref is_nullable(expr* r); + /* + make the derivative of r wrt the given element ele + */ expr_ref mk_derivative(expr* ele, expr* r); + /* + make the derivative of r wrt the canonical variable v0 = (:var 0), + for example mk_derivative(a+) = (if (v0 = 'a') then a* else []) + */ + expr_ref mk_derivative(expr* r); // heuristic elimination of element from condition that comes form a derivative. // special case optimization for conjunctions of equalities, disequalities and ranges. diff --git a/src/ast/rewriter/seq_skolem.cpp b/src/ast/rewriter/seq_skolem.cpp index ecefe8374..90b33c24e 100644 --- a/src/ast/rewriter/seq_skolem.cpp +++ b/src/ast/rewriter/seq_skolem.cpp @@ -28,8 +28,8 @@ skolem::skolem(ast_manager& m, th_rewriter& rw): m_tail = "seq.tail"; m_seq_first = "seq.first"; m_seq_last = "seq.last"; - m_indexof_left = "seq.idx.left"; - m_indexof_right = "seq.idx.right"; + m_indexof_left = "seq.idx.l"; + m_indexof_right = "seq.idx.r"; m_aut_step = "aut.step"; m_pre = "seq.pre"; // (seq.pre s l): prefix of string s of length l m_post = "seq.post"; // (seq.post s l): suffix of string s of length k, based on extract starting at index i of length l diff --git a/src/ast/rewriter/seq_skolem.h b/src/ast/rewriter/seq_skolem.h index a7d4be917..1a5928a0b 100644 --- a/src/ast/rewriter/seq_skolem.h +++ b/src/ast/rewriter/seq_skolem.h @@ -77,8 +77,10 @@ namespace seq { expr_ref mk_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk(m_indexof_left, t, s, offset); } expr_ref mk_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk(m_indexof_right, t, s, offset); } - expr_ref mk_last_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.last_indexof_left", t, s, offset); } - expr_ref mk_last_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.last_indexof_right", t, s, offset); } + expr_ref mk_contains_left(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.cnt.l", t, s, offset); } + expr_ref mk_contains_right(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.cnt.r", t, s, offset); } + expr_ref mk_last_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.lidx.l", t, s, offset); } + expr_ref mk_last_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.lidx.r", t, s, offset); } expr_ref mk_tail(expr* s, expr* i) { return mk(m_tail, s, i); } expr_ref mk_post(expr* s, expr* i) { return mk(m_post, s, i); } diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 69ec244d5..ebae138e3 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -839,7 +839,7 @@ bool seq_util::str::is_nth_i(expr const* n, expr*& s, unsigned& idx) const { return arith_util(m).is_unsigned(i, idx); } -app* seq_util::str::mk_nth_i(expr* s, unsigned i) const { +app* seq_util::str::mk_nth_c(expr* s, unsigned i) const { return mk_nth_i(s, arith_util(m).mk_int(i)); } @@ -854,6 +854,48 @@ void seq_util::str::get_concat(expr* e, expr_ref_vector& es) const { } } +/* +Returns true if s is an expression of the form (l = |u|) |u|-k or (-k)+|u| or |u|+(-k). +Also returns true and assigns k=0 and l=s if s is |u|. +*/ +bool seq_util::str::is_len_sub(expr const* s, expr*& l, expr*& u, rational& k) const { + expr* x; + rational v; + arith_util a(m); + if (is_length(s, l)) { + k = 0; + return true; + } + else if (a.is_sub(s, l, x) && is_length(l, u) && a.is_numeral(x, v) && v.is_nonneg()) { + k = v; + return true; + } + else if (a.is_add(s, l, x) && is_length(l, u) && a.is_numeral(x, v) && v.is_nonpos()) { + k = - v; + return true; + } + else if (a.is_add(s, x, l) && is_length(l, u) && a.is_numeral(x, v) && v.is_nonpos()) { + k = - v; + return true; + } + else + return false; +} + +bool seq_util::str::is_unit_string(expr const* s, expr_ref& c) const { + zstring z; + expr* ch = nullptr; + if (is_string(s, z) && z.length() == 1) { + c = mk_char(z[0]); + return true; + } + else if (is_unit(s, ch)) { + c = ch; + return true; + } + return false; +} + void seq_util::str::get_concat_units(expr* e, expr_ref_vector& es) const { expr* e1, *e2; while (is_concat(e, e1, e2)) { @@ -876,8 +918,6 @@ app* seq_util::str::mk_is_empty(expr* s) const { return m.mk_eq(s, mk_empty(s->get_sort())); } - - unsigned seq_util::str::min_length(expr* s) const { SASSERT(u.is_seq(s)); unsigned result = 0; @@ -892,7 +932,10 @@ unsigned seq_util::str::min_length(expr* s) const { return 0u; }; while (is_concat(s, s1, s2)) { - result += get_length(s1); + if (is_concat(s1)) + result += min_length(s1); + else + result += get_length(s1); s = s2; } result += get_length(s); @@ -920,7 +963,10 @@ unsigned seq_util::str::max_length(expr* s) const { return UINT_MAX; }; while (is_concat(s, s1, s2)) { - result = u.max_plus(get_length(s1), result); + if (is_concat(s1)) + result = u.max_plus(max_length(s1), result); + else + result = u.max_plus(get_length(s1), result); s = s2; } result = u.max_plus(get_length(s), result); @@ -1065,38 +1111,70 @@ app* seq_util::rex::mk_epsilon(sort* seq_sort) { /* Produces compact view of concrete concatenations such as (abcd). */ -std::ostream& seq_util::rex::pp::compact_helper_seq(std::ostream& out, expr* s) const { - SASSERT(re.u.is_seq(s)); +bool seq_util::rex::pp::print_seq(std::ostream& out, expr* s) const { zstring z; + expr* x, * j, * k, * l, * i, * x_; if (re.u.str.is_empty(s)) out << "()"; - else if (re.u.str.is_unit(s)) - seq_unit(out, s); else if (re.u.str.is_concat(s)) { expr_ref_vector es(re.m); re.u.str.get_concat(s, es); for (expr* e : es) - compact_helper_seq(out, e); + print(out, e); } else if (re.u.str.is_string(s, z)) { for (unsigned i = 0; i < z.length(); i++) out << (char)z[i]; } - //using braces to indicate 'full' output - //for example an uninterpreted constant X will be printed as {X} - //while a unit sequence "X" will be printed as X - //thus for example (concat "X" "Y" Z "W") where Z is uninterpreted is printed as XY{Z}W - else out << "{" << mk_pp(s, re.m) << "}"; - return out; + else if (re.u.str.is_at(s, x, i)) + print(out, x) << "@", print(out, i); + else if (re.u.str.is_extract(s, x, j, k)) { + rational jv, iv; + print(out, x); + if (arith_util(re.m).is_numeral(j, jv)) { + if (arith_util(re.m).is_numeral(k, iv)) { + // output X[j,k] + out << "[" << jv.get_int32() << "," << jv.get_int32() << "]"; + } + else if (arith_util(re.m).is_sub(k, l, i) && re.u.str.is_length(l, x_) && x == x_ && + arith_util(re.m).is_numeral(i, iv) && iv == jv) { + // case X[j,|X|-j] is denoted by X[j..] + out << "[" << jv.get_int32() << "..]"; + } + else if (((arith_util(re.m).is_add(k, l, i) && re.u.str.is_length(l, x_)) || + (arith_util(re.m).is_add(k, i, l) && re.u.str.is_length(l, x_))) && x == x_ && + arith_util(re.m).is_numeral(i, iv) && iv.get_int32() + jv.get_int32() == 0) { + // case X[j,|X|-j] is denoted by X[j..] + out << "[" << jv.get_int32() << "..]"; + } + else { + out << "[" << jv.get_int32() << ","; + print(out, k); + out << "]"; + } + } + else { + out << "["; + print(out, j); + out << ","; + print(out, k); + out << "]"; + } + } + else + return false; + return true; } /* Produces output such as [a-z] for a range. */ -std::ostream& seq_util::rex::pp::compact_helper_range(std::ostream& out, expr* s1, expr* s2) const { +std::ostream& seq_util::rex::pp::print_range(std::ostream& out, expr* s1, expr* s2) const { out << "["; - seq_unit(out, s1) << "-"; - seq_unit(out, s2) << "]"; + print(out, s1); + out << "-"; + print(out, s2); + out << "]"; return out; } @@ -1111,9 +1189,10 @@ bool seq_util::rex::pp::can_skip_parenth(expr* r) const { /* Specialize output for a unit sequence converting to visible ASCII characters if possible. */ -std::ostream& seq_util::rex::pp::seq_unit(std::ostream& out, expr* s) const { - expr* e; +bool seq_util::rex::pp::print_unit(std::ostream& out, expr* s) const { + expr* e, * i; unsigned n = 0; + if ((re.u.str.is_unit(s, e) && re.u.is_const_char(e, n)) || re.u.is_const_char(s, n)) { char c = (char)n; if (c == '\n') @@ -1122,22 +1201,21 @@ std::ostream& seq_util::rex::pp::seq_unit(std::ostream& out, expr* s) const { out << "\\r"; else if (c == '\f') out << "\\f"; - else if (c == ' ') - out << "\\s"; - else if (c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || c == '.' || c == '\\') - out << "\\" << c; - else if (32 < n && n < 127) { + else if (32 <= n && n < 127 && n != '\"' && n != ' ' + && n != '\\' && n != '\'' && n != '?' && n != '.' && n != '(' && n != ')' && n != '[' && n != ']' + && n != '{' && n != '}' && n != '&') { if (html_encode) { if (c == '<') out << "<"; else if (c == '>') out << ">"; - else if (c == '&') - out << "&"; - else if (c == '\"') - out << """; + //else if (c == '&') + // out << "&"; + //else if (c == '\"') + // out << """; else - out << "\\x" << std::hex << n; + //out << "\\x" << std::hex << n; + out << c; } else out << c; @@ -1148,95 +1226,193 @@ std::ostream& seq_util::rex::pp::seq_unit(std::ostream& out, expr* s) const { out << "\\x" << std::hex << n; else if (n <= 0xFFF) out << "\\u0" << std::hex << n; - else + else out << "\\u" << std::hex << n; } + else if (re.u.str.is_nth_i(s, e, i)) { + print(out, e) << "["; + print(out, i) << "]"; + } + else if (re.u.str.is_length(s, e)) + print(out << "|", e) << "|"; else - out << "{" << mk_pp(s, re.m) << "}"; - return out; + return false; + return true; } /* - Pretty prints the regex r into the out stream + Pretty prints the regex r into the ostream out */ -std::ostream& seq_util::rex::pp::display(std::ostream& out) const { +std::ostream& seq_util::rex::pp::print(std::ostream& out, expr* e) const { expr* r1 = nullptr, * r2 = nullptr, * s = nullptr, * s2 = nullptr; unsigned lo = 0, hi = 0; - if (re.u.is_char(e)) - return seq_unit(out, e); - else if (re.u.is_seq(e)) - return compact_helper_seq(out, e); + arith_util a(re.m); + rational v; + if (!e) + out << "null"; + else if (print_unit(out, e)) + ; + else if (print_seq(out, e)) + ; else if (re.is_full_char(e)) - return out << "."; + out << "."; else if (re.is_full_seq(e)) - return out << ".*"; + out << ".*"; else if (re.is_to_re(e, s)) - return compact_helper_seq(out, s); - else if (re.is_range(e, s, s2)) - return compact_helper_range(out, s, s2); + print(out, s); + else if (re.is_range(e, s, s2)) + print_range(out, s, s2); else if (re.is_epsilon(e)) - return out << "()"; + // ε = epsilon + out << (html_encode ? "ε" : "()"); else if (re.is_empty(e)) - return out << "[]"; - else if (re.is_concat(e, r1, r2)) - return out << pp(re, r1) << pp(re, r2); - else if (re.is_union(e, r1, r2)) - return out << "(" << pp(re, r1) << "|" << pp(re, r2) << ")"; - else if (re.is_intersection(e, r1, r2)) - return out << "(" << pp(re, r1) << "&" /*(html_encode ? ")&(" : ")&(")*/ << pp(re, r2) << ")"; + // ∅ = emptyset + out << (html_encode ? "∅" : "[]"); + else if (re.is_concat(e, r1, r2)) { + print(out, r1); + print(out, r2); + } + else if (re.is_antimorov_union(e, r1, r2) || re.is_union(e, r1, r2)) { + out << "("; + print(out, r1); + out << (html_encode ? "⋃" : "|"); + print(out, r2); + out << ")"; + } + else if (re.is_intersection(e, r1, r2)) { + out << "("; + print(out, r1); + out << (html_encode ? "⋂" : "&"); + print(out, r2); + out << ")"; + } else if (re.is_complement(e, r1)) { + out << "~"; if (can_skip_parenth(r1)) - return out << "~" << pp(re, r1); - else - return out << "~(" << pp(re, r1) << ")"; + print(out, r1); + else { + out << "("; + print(out, r1); + out << ")"; + } } else if (re.is_plus(e, r1)) { - if (can_skip_parenth(r1)) - return out << pp(re, r1) << "+"; - else - return out << "(" << pp(re, r1) << ")+"; + if (can_skip_parenth(r1)) { + print(out, r1); + out << "+"; + } + else { + out << "("; + print(out, r1); + out << ")+"; + } } else if (re.is_star(e, r1)) { - if (can_skip_parenth(r1)) - return out << pp(re, r1) << "*"; - else - return out << "(" << pp(re, r1) << ")*"; + if (can_skip_parenth(r1)) { + print(out, r1); + out << "*"; + } + else { + out << "("; + print(out, r1); + out << ")*"; + } } else if (re.is_loop(e, r1, lo)) { - if (can_skip_parenth(r1)) - return out << pp(re, r1) << "{" << lo << ",}"; - else - return out << "(" << pp(re, r1) << "){" << lo << ",}"; + if (can_skip_parenth(r1)) + print(out, r1) << "{" << lo << ",}"; + else { + out << "("; + print(out, r1); + out << "){" << lo << ",}"; + } } else if (re.is_loop(e, r1, lo, hi)) { if (can_skip_parenth(r1)) { + print(out, r1); if (lo == hi) - return out << pp(re, r1) << "{" << lo << "}"; - else - return out << pp(re, r1) << "{" << lo << "," << hi << "}"; + out << "{" << lo << "}"; + else + out << "{" << lo << "," << hi << "}"; } else { + out << "("; + print(out, r1); if (lo == hi) - return out << "(" << pp(re, r1) << "){" << lo << "}"; + out << "){" << lo << "}"; else - return out << "(" << pp(re, r1) << "){" << lo << "," << hi << "}"; + out << "){" << lo << "," << hi << "}"; } } - else if (re.is_diff(e, r1, r2)) - return out << "(" << pp(re, r1) << ")\\(" << pp(re, r2) << ")"; - else if (re.m.is_ite(e, s, r1, r2)) - return out << "if(" << mk_pp(s, re.m) << "," << pp(re, r1) << "," << pp(re, r2) << ")"; + else if (re.is_diff(e, r1, r2)) { + out << "("; + print(out, r1); + out << ")\\("; + print(out, r2); + out << ")"; + } + else if (re.m.is_ite(e, s, r1, r2)) { + out << (html_encode ? "(𝐢𝐟 " : "(if "); + print(out, s); + out << (html_encode ? " 𝐭𝗵𝐞𝐧 " : " then "); + print(out, r1); + out << (html_encode ? " 𝐞𝐥𝘀𝐞 " : " else "); + print(out, r2); + out << ")"; + } else if (re.is_opt(e, r1)) { if (can_skip_parenth(r1)) - return out << pp(re, r1) << "?"; - else - return out << "(" << pp(re, r1) << ")?"; + print(out, r1) << "?"; + else { + out << "("; + print(out, r1); + out << ")?"; + } + } + else if (re.is_reverse(e, r1)) { + out << "(reverse "; + print(out, r1); + out << ")"; + } + else if (re.m.is_eq(e, r1, r2)) { + out << "("; + print(out, r1); + out << " = "; + print(out, r2); + out << ")"; + } + else if (re.m.is_not(e, r1)) { + out << "!"; + print(out, r1); + } + else if (a.is_add(e, s, s2) && a.is_numeral(s, v) && v < 0) + print(out, s2) << " - " << -v; + else if (a.is_add(e, s, s2) && a.is_numeral(s2, v) && v < 0) + print(out, s) << " - " << -v; + else if (a.is_add(e, s, s2)) + print(out, s) << " + ", print(out, s2); + else if (a.is_sub(e, s, s2) && a.is_numeral(s2, v) && v > 0) + print(out, s) << " - " << v; + else if (a.is_le(e, s, s2)) + print(out << "(", s) << " <= ", print(out, s2) << ")"; + else if (re.m.is_value(e)) + out << mk_pp(e, re.m); + else if (is_app(e) && to_app(e)->get_num_args() == 0) + out << mk_pp(e, re.m); + else if (is_app(e)) { + out << "(" << to_app(e)->get_decl()->get_name(); + for (expr* arg : *to_app(e)) + print(out << " ", arg); + out << ")"; } - else if (re.is_reverse(e, r1)) - return out << "reverse(" << pp(re, r1) << ")"; else - // Else: derivative or is_of_pred - return out << "{" << mk_pp(e, re.m) << "}"; + // for all remaining cases use the default pretty printer + out << mk_pp(e, re.m); + return out; +} + +std::ostream& seq_util::rex::pp::display(std::ostream& out) const { + return print(out, ex); } /* @@ -1244,7 +1420,16 @@ std::ostream& seq_util::rex::pp::display(std::ostream& out) const { */ std::string seq_util::rex::to_str(expr* r) const { std::ostringstream out; - out << pp(u.re, r); + pp(u.re, r, false).display(out); + return out.str(); +} + +/* + Pretty prints the regex r into the output string that is htmlencoded +*/ +std::string seq_util::rex::to_strh(expr* r) const { + std::ostringstream out; + pp(u.re, r, true).display(out); return out.str(); } @@ -1290,7 +1475,7 @@ seq_util::rex::info seq_util::rex::get_info_rec(expr* e) const { else result = mk_info_rec(to_app(e)); m_infos.setx(e->get_id(), result, invalid_info); - STRACE("re_info", tout << "compute_info(" << pp(u.re, e) << ")=" << result << std::endl;); + STRACE("re_info", tout << "compute_info(" << pp(u.re, e, false) << ")=" << result << std::endl;); return result; } @@ -1518,7 +1703,13 @@ seq_util::rex::info seq_util::rex::info::orelse(seq_util::rex::info const& i) co // unsigned ite_min_length = std::min(min_length, i.min_length); // lbool ite_nullable = (nullable == i.nullable ? nullable : l_undef); // TBD: whether ite is interpreted or not depends on whether the condition is interpreted and both branches are interpreted - return info(false, false, false, false, normalized && i.normalized, monadic && i.monadic, singleton && i.singleton, nullable, std::min(min_length, i.min_length), std::max(star_height, i.star_height)); + return info(false, false, false, false, + normalized && i.normalized, + monadic && i.monadic, + singleton && i.singleton, + ((nullable == l_true && i.nullable == l_true) ? l_true : ((nullable == l_false && i.nullable == l_false) ? l_false : l_undef)), + std::min(min_length, i.min_length), + std::max(star_height, i.star_height)); } else return i; diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index f9589fee6..f194e6a67 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -286,7 +286,8 @@ public: app* mk_at(expr* s, expr* i) const { expr* es[2] = { s, i }; return m.mk_app(m_fid, OP_SEQ_AT, 2, es); } app* mk_nth(expr* s, expr* i) const { expr* es[2] = { s, i }; return m.mk_app(m_fid, OP_SEQ_NTH, 2, es); } app* mk_nth_i(expr* s, expr* i) const { expr* es[2] = { s, i }; return m.mk_app(m_fid, OP_SEQ_NTH_I, 2, es); } - app* mk_nth_i(expr* s, unsigned i) const; + app* mk_nth_u(expr* s, expr* i) const { expr* es[2] = { s, i }; return m.mk_app(m_fid, OP_SEQ_NTH_U, 2, es); } + app* mk_nth_c(expr* s, unsigned i) const; app* mk_substr(expr* a, expr* b, expr* c) const { expr* es[3] = { a, b, c }; return m.mk_app(m_fid, OP_SEQ_EXTRACT, 3, es); } app* mk_contains(expr* a, expr* b) const { expr* es[2] = { a, b }; return m.mk_app(m_fid, OP_SEQ_CONTAINS, 2, es); } @@ -350,6 +351,13 @@ public: bool is_from_code(expr const* n) const { return is_app_of(n, m_fid, OP_STRING_FROM_CODE); } bool is_to_code(expr const* n) const { return is_app_of(n, m_fid, OP_STRING_TO_CODE); } + bool is_len_sub(expr const* n, expr*& l, expr*& u, rational& k) const; + + /* + tests if s is a single character string(c) or a unit (c) + */ + bool is_unit_string(expr const* s, expr_ref& c) const; + bool is_string_term(expr const * n) const { return u.is_string(n->get_sort()); } @@ -530,7 +538,20 @@ public: bool is_loop(expr const* n) const { return is_app_of(n, m_fid, OP_RE_LOOP); } bool is_empty(expr const* n) const { return is_app_of(n, m_fid, OP_RE_EMPTY_SET); } bool is_full_char(expr const* n) const { return is_app_of(n, m_fid, OP_RE_FULL_CHAR_SET); } - bool is_full_seq(expr const* n) const { return is_app_of(n, m_fid, OP_RE_FULL_SEQ_SET); } + bool is_full_seq(expr const* n) const { + expr* s; + return is_app_of(n, m_fid, OP_RE_FULL_SEQ_SET) || (is_star(n, s) && is_full_char(s)); + } + bool is_dot_plus(expr const* n) const { + expr* s, * t; + if (is_plus(n, s) && is_full_char(s)) + return true; + if (is_concat(n, s, t)) { + if ((is_full_char(s) && is_full_seq(t)) || (is_full_char(t) && is_full_seq(s))) + return true; + } + return false; + } bool is_of_pred(expr const* n) const { return is_app_of(n, m_fid, OP_RE_OF_PRED); } bool is_reverse(expr const* n) const { return is_app_of(n, m_fid, OP_RE_REVERSE); } bool is_derivative(expr const* n) const { return is_app_of(n, m_fid, OP_RE_DERIVATIVE); } @@ -559,18 +580,32 @@ public: app* mk_epsilon(sort* seq_sort); info get_info(expr* r) const; std::string to_str(expr* r) const; + std::string to_strh(expr* r) const; + + expr_ref mk_ite_simplify(expr* c, expr* t, expr* e) + { + expr_ref result(m); + if (m.is_true(c) || t == e) + result = t; + else if (m.is_false(c)) + result = e; + else + result = m.mk_ite(c, t, e); + return result; + } class pp { seq_util::rex& re; - expr* e; + expr* ex; bool html_encode; bool can_skip_parenth(expr* r) const; - std::ostream& seq_unit(std::ostream& out, expr* s) const; - std::ostream& compact_helper_seq(std::ostream& out, expr* s) const; - std::ostream& compact_helper_range(std::ostream& out, expr* s1, expr* s2) const; + bool print_unit(std::ostream& out, expr* s) const; + bool print_seq(std::ostream& out, expr* s) const; + std::ostream& print_range(std::ostream& out, expr* s1, expr* s2) const; + std::ostream& print(std::ostream& out, expr* e) const; public: - pp(seq_util::rex& r, expr* e, bool html = false) : re(r), e(e), html_encode(html) {} + pp(seq_util::rex& re, expr* ex, bool html) : re(re), ex(ex), html_encode(html) {} std::ostream& display(std::ostream&) const; }; }; diff --git a/src/ast/special_relations_decl_plugin.h b/src/ast/special_relations_decl_plugin.h index 4ce7dfec2..d804fa179 100644 --- a/src/ast/special_relations_decl_plugin.h +++ b/src/ast/special_relations_decl_plugin.h @@ -87,6 +87,7 @@ public: bool is_special_relation(app* e) const { return is_special_relation(e->get_decl()); } sr_property get_property(func_decl* f) const; sr_property get_property(app* e) const { return get_property(e->get_decl()); } + func_decl* get_relation(func_decl* f) const { SASSERT(is_special_relation(f)); return to_func_decl(f->get_parameter(0).get_ast()); } func_decl* mk_to_decl(func_decl* f) { return mk_rel_decl(f, OP_SPECIAL_RELATION_TO); } func_decl* mk_po_decl(func_decl* f) { return mk_rel_decl(f, OP_SPECIAL_RELATION_PO); } diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 1e5ce62f3..845ac645c 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -1828,9 +1828,8 @@ void cmd_context::add_declared_functions(model& mdl) { mdl.register_decl(f, fi); } } - mdl.add_rec_funs(); } - + mdl.add_rec_funs(); } void cmd_context::display_sat_result(lbool r) { diff --git a/src/math/hilbert/heap_trie.h b/src/math/hilbert/heap_trie.h index 8b9f60f73..7d4179de8 100644 --- a/src/math/hilbert/heap_trie.h +++ b/src/math/hilbert/heap_trie.h @@ -270,6 +270,7 @@ public: class check_value { public: virtual bool operator()(Value const& v) = 0; + virtual ~check_value() = default; }; bool find_le(Key const* keys, check_value& check) { diff --git a/src/math/lp/column_namer.h b/src/math/lp/column_namer.h index cffae412c..cef58ed21 100644 --- a/src/math/lp/column_namer.h +++ b/src/math/lp/column_namer.h @@ -23,6 +23,7 @@ Revision History: namespace lp { class column_namer { public: + virtual ~column_namer() = default; virtual std::string get_variable_name(unsigned j) const = 0; template std::ostream & print_row(const row_strip & row, std::ostream & out) const { diff --git a/src/math/lp/factorization.h b/src/math/lp/factorization.h index a021a2a36..b233894ad 100644 --- a/src/math/lp/factorization.h +++ b/src/math/lp/factorization.h @@ -120,6 +120,8 @@ struct factorization_factory { m_vars(vars), m_monic(m) { } + virtual ~factorization_factory() = default; + bool_vector get_mask() const { // we keep the last element always in the first factor to avoid // repeating a pair twice, that is why m_mask is shorter by one then m_vars diff --git a/src/math/lp/lar_solver.cpp b/src/math/lp/lar_solver.cpp index b31713094..561a94e99 100644 --- a/src/math/lp/lar_solver.cpp +++ b/src/math/lp/lar_solver.cpp @@ -16,6 +16,9 @@ namespace lp { lp_settings const& lar_solver::settings() const { return m_settings; } + statistics& lar_solver::stats() { return m_settings.stats(); } + + void lar_solver::updt_params(params_ref const& _p) { smt_params_helper p(_p); set_track_pivoted_rows(p.arith_bprop_on_pivoted_rows()); @@ -23,17 +26,9 @@ namespace lp { m_settings.updt_params(_p); } - - void clear() { - lp_assert(false); // not implemented - } - lar_solver::lar_solver() : - m_status(lp_status::UNKNOWN), m_crossed_bounds_column(-1), m_mpq_lar_core_solver(m_settings, *this), - m_int_solver(nullptr), - m_need_register_terms(false), m_var_register(false), m_term_register(true), m_constraints(*this) {} @@ -197,11 +192,11 @@ namespace lp { void lar_solver::set_status(lp_status s) { m_status = s; } lp_status lar_solver::find_feasible_solution() { - m_settings.stats().m_make_feasible++; - if (A_r().column_count() > m_settings.stats().m_max_cols) - m_settings.stats().m_max_cols = A_r().column_count(); - if (A_r().row_count() > m_settings.stats().m_max_rows) - m_settings.stats().m_max_rows = A_r().row_count(); + stats().m_make_feasible++; + if (A_r().column_count() > stats().m_max_cols) + stats().m_max_cols = A_r().column_count(); + if (A_r().row_count() > stats().m_max_rows) + stats().m_max_rows = A_r().row_count(); if (strategy_is_undecided()) decide_on_strategy_and_adjust_initial_state(); @@ -248,7 +243,7 @@ namespace lp { m_constraints.push(); m_usage_in_terms.push(); } - + void lar_solver::clean_popped_elements(unsigned n, u_set& set) { vector to_remove; for (unsigned j : set) @@ -269,9 +264,8 @@ namespace lp { m_crossed_bounds_column.pop(k); unsigned n = m_columns_to_ul_pairs.peek_size(k); m_var_register.shrink(n); - if (m_settings.use_tableau()) { + if (m_settings.use_tableau()) pop_tableau(); - } lp_assert(A_r().column_count() == n); TRACE("lar_solver_details", for (unsigned j = 0; j < n; j++) { @@ -285,6 +279,10 @@ namespace lp { clean_popped_elements(n, m_columns_with_changed_bounds); clean_popped_elements(n, m_incorrect_columns); + for (auto rid : m_row_bounds_to_replay) + insert_row_with_changed_bounds(rid); + m_row_bounds_to_replay.reset(); + unsigned m = A_r().row_count(); clean_popped_elements(m, m_rows_with_changed_bounds); clean_inf_set_of_r_solver_after_pop(); @@ -633,6 +631,9 @@ namespace lp { left_side.push_back(std::make_pair(p.second, p.first)); } + void lar_solver::insert_row_with_changed_bounds(unsigned rid) { + m_rows_with_changed_bounds.insert(rid); + } void lar_solver::detect_rows_of_bound_change_column_for_nbasic_column(unsigned j) { if (A_r().row_count() != m_column_buffer.data_size()) @@ -643,14 +644,14 @@ namespace lp { m_mpq_lar_core_solver.m_r_solver.solve_Bd(j, m_column_buffer); for (unsigned i : m_column_buffer.m_index) - m_rows_with_changed_bounds.insert(i); + insert_row_with_changed_bounds(i); } void lar_solver::detect_rows_of_bound_change_column_for_nbasic_column_tableau(unsigned j) { for (auto& rc : m_mpq_lar_core_solver.m_r_A.m_columns[j]) - m_rows_with_changed_bounds.insert(rc.var()); + insert_row_with_changed_bounds(rc.var()); } bool lar_solver::use_tableau() const { return m_settings.use_tableau(); } @@ -743,7 +744,7 @@ namespace lp { void lar_solver::detect_rows_with_changed_bounds_for_column(unsigned j) { if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { - m_rows_with_changed_bounds.insert(m_mpq_lar_core_solver.m_r_heading[j]); + insert_row_with_changed_bounds(m_mpq_lar_core_solver.m_r_heading[j]); return; } @@ -793,7 +794,7 @@ namespace lp { update_x_and_inf_costs_for_columns_with_changed_bounds(); m_mpq_lar_core_solver.solve(); set_status(m_mpq_lar_core_solver.m_r_solver.get_status()); - lp_assert(((m_settings.stats().m_make_feasible% 100) != 0) || m_status != lp_status::OPTIMAL || all_constraints_hold()); + lp_assert(((stats().m_make_feasible% 100) != 0) || m_status != lp_status::OPTIMAL || all_constraints_hold()); } @@ -974,9 +975,7 @@ namespace lp { bool lar_solver::the_left_sides_sum_to_zero(const vector>& evidence) const { std::unordered_map coeff_map; - for (auto& it : evidence) { - mpq coeff = it.first; - constraint_index con_ind = it.second; + for (auto const & [coeff, con_ind] : evidence) { lp_assert(m_constraints.valid_index(con_ind)); register_in_map(coeff_map, m_constraints[con_ind], coeff); } @@ -1337,7 +1336,7 @@ namespace lp { void lar_solver::mark_rows_for_bound_prop(lpvar j) { auto& column = A_r().m_columns[j]; for (auto const& r : column) - m_rows_with_changed_bounds.insert(r.var()); + insert_row_with_changed_bounds(r.var()); } @@ -1659,7 +1658,7 @@ namespace lp { m_mpq_lar_core_solver.m_r_heading.push_back(m_mpq_lar_core_solver.m_r_basis.size()); m_mpq_lar_core_solver.m_r_basis.push_back(j); if (m_settings.bound_propagation()) - m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + insert_row_with_changed_bounds(A_r().row_count() - 1); } else { m_mpq_lar_core_solver.m_r_heading.push_back(-static_cast(m_mpq_lar_core_solver.m_r_nbasis.size()) - 1); @@ -1755,7 +1754,7 @@ namespace lp { if (use_tableau() && !coeffs.empty()) { add_row_from_term_no_constraint(m_terms.back(), ret); if (m_settings.bound_propagation()) - m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + insert_row_with_changed_bounds(A_r().row_count() - 1); } lp_assert(m_var_register.size() == A_r().column_count()); if (m_need_register_terms) { diff --git a/src/math/lp/lar_solver.h b/src/math/lp/lar_solver.h index 6e61354ff..b4c69d783 100644 --- a/src/math/lp/lar_solver.h +++ b/src/math/lp/lar_solver.h @@ -76,13 +76,13 @@ class lar_solver : public column_namer { //////////////////// fields ////////////////////////// lp_settings m_settings; - lp_status m_status; + lp_status m_status = lp_status::UNKNOWN; stacked_value m_simplex_strategy; // such can be found at the initialization step: u < l stacked_value m_crossed_bounds_column; lar_core_solver m_mpq_lar_core_solver; - int_solver * m_int_solver; - bool m_need_register_terms; + int_solver * m_int_solver = nullptr; + bool m_need_register_terms = false; var_register m_var_register; var_register m_term_register; stacked_vector m_columns_to_ul_pairs; @@ -90,6 +90,8 @@ class lar_solver : public column_namer { // the set of column indices j such that bounds have changed for j u_set m_columns_with_changed_bounds; u_set m_rows_with_changed_bounds; + unsigned_vector m_row_bounds_to_replay; + u_set m_basic_columns_with_changed_cost; // these are basic columns with the value changed, so the the corresponding row in the tableau // does not sum to zero anymore @@ -164,7 +166,6 @@ class lar_solver : public column_namer { void adjust_initial_state_for_lu(); void adjust_initial_state_for_tableau_rows(); void fill_last_row_of_A_d(static_matrix & A, const lar_term* ls); - void clear(); bool use_lu() const; bool sizes_are_correct() const; bool implied_bound_is_correctly_explained(implied_bound const & be, const vector> & explanation) const; @@ -219,6 +220,7 @@ class lar_solver : public column_namer { void change_basic_columns_dependend_on_a_given_nb_column(unsigned j, const numeric_pair & delta); void update_x_and_inf_costs_for_column_with_changed_bounds(unsigned j); unsigned num_changed_bounds() const { return m_rows_with_changed_bounds.size(); } + void insert_row_with_changed_bounds(unsigned rid); void detect_rows_with_changed_bounds_for_column(unsigned j); void detect_rows_with_changed_bounds(); void set_value_for_nbasic_column(unsigned j, const impq & new_val); @@ -368,20 +370,19 @@ public: // these two loops should be run sequentially // since the first loop might change column bounds // and add fixed columns this way - if (settings().cheap_eqs()) { + if (settings().propagate_eqs()) { bp.clear_for_eq(); for (unsigned i : m_rows_with_changed_bounds) { - calculate_cheap_eqs_for_row(i, bp); + unsigned offset_eqs = stats().m_offset_eqs; + bp.cheap_eq_tree(i); if (settings().get_cancel_flag()) return; + if (stats().m_offset_eqs > offset_eqs) + m_row_bounds_to_replay.push_back(i); } } m_rows_with_changed_bounds.clear(); } - template - void calculate_cheap_eqs_for_row(unsigned i, lp_bound_propagator & bp) { - bp.cheap_eq_tree(i); - } bool is_fixed(column_index const& j) const { return column_is_fixed(j); } inline column_index to_column_index(unsigned v) const { return column_index(external_to_column_index(v)); } @@ -515,6 +516,8 @@ public: unsigned column_to_reported_index(unsigned j) const; lp_settings & settings(); lp_settings const & settings() const; + statistics& stats(); + void updt_params(params_ref const& p); column_type get_column_type(unsigned j) const { return m_mpq_lar_core_solver.m_column_types()[j]; } const impq & get_lower_bound(unsigned j) const { return m_mpq_lar_core_solver.m_r_lower_bounds()[j]; } diff --git a/src/math/lp/lar_term.h b/src/math/lp/lar_term.h index 8dcb16684..3ef424e24 100644 --- a/src/math/lp/lar_term.h +++ b/src/math/lp/lar_term.h @@ -155,7 +155,7 @@ public: }; class const_iterator { - u_map< mpq>::iterator m_it; + u_map::iterator m_it; public: ival operator*() const { return ival(m_it->m_key, m_it->m_value); } const_iterator operator++() { const_iterator i = *this; m_it++; return i; } diff --git a/src/math/lp/lp_bound_propagator.h b/src/math/lp/lp_bound_propagator.h index fec650293..1ea872517 100644 --- a/src/math/lp/lp_bound_propagator.h +++ b/src/math/lp/lp_bound_propagator.h @@ -72,14 +72,15 @@ class lp_bound_propagator { static int other(int x, int y, int z) { SASSERT(x == z || y == z); return x == z ? y : x; } std::ostream& print_vert(std::ostream & out, const vertex* v) const { out << "(c = " << v->column() << ", parent = {"; - if (v->parent()) { out << "(" << v->parent()->column() << ")";} - else { out << "null"; } + if (v->parent()) + out << "(" << v->parent()->column() << ")"; + else + out << "null"; out << "} , lvl = " << v->level(); - if (m_pol.contains(v->column())) { + if (m_pol.contains(v->column())) out << (pol(v) == -1? " -":" +"); - } else { + else out << " not in m_pol"; - } out << ')'; return out; } @@ -87,13 +88,13 @@ class lp_bound_propagator { hashtable m_visited_rows; hashtable m_visited_columns; u_map m_vertices; - vertex* m_root; + vertex* m_root = nullptr; // At some point we can find a row with a single vertex non fixed vertex // then we can fix the whole tree, // by adjusting the vertices offsets, so they become absolute. // If the tree is fixed then in addition to checking with the m_vals_to_verts // we are going to check with the m_fixed_var_tables. - const vertex* m_fixed_vertex; + const vertex* m_fixed_vertex = nullptr; explanation m_fixed_vertex_explanation; // a pair (o, j) belongs to m_vals_to_verts iff x[j] = x[m_root->column()] + o map, default_eq> m_vals_to_verts; @@ -111,19 +112,199 @@ class lp_bound_propagator { T& m_imp; vector m_ibounds; + + + + map, default_eq> m_val2fixed_row; + + void try_add_equation_with_internal_fixed_tables(unsigned r1, vertex const* v) { + SASSERT(m_fixed_vertex); + if (v != m_root) + return; + unsigned v1 = v->column(); + unsigned r2 = UINT_MAX; + if (!m_val2fixed_row.find(val(v1), r2) || r2 >= lp().row_count()) { + m_val2fixed_row.insert(val(v1), r1); + return; + } + unsigned v2, v3; + int polarity; + if (!is_tree_offset_row(r2, v2, v3, polarity) || !not_set(v3) || + is_int(v1) != is_int(v2) || val(v1) != val(v2)) { + m_val2fixed_row.insert(val(v1), r1); + return; + } + + explanation ex; + explain_fixed_in_row(r1, ex); + explain_fixed_in_row(r2, ex); + add_eq_on_columns(ex, v1, v2, true); + } + + void try_add_equation_with_lp_fixed_tables(unsigned row_index, const vertex *v) { + SASSERT(m_fixed_vertex); + unsigned v_j = v->column(); + unsigned j = null_lpvar; + if (!lp().find_in_fixed_tables(val(v_j), is_int(v_j), j)) { + // try_add_equation_with_internal_fixed_tables(row_index, v); + return; + } + + TRACE("cheap_eq", + tout << "v_j = "; lp().print_column_info(v_j, tout) << std::endl; + tout << "v = "; print_vert(tout, v) << std::endl; + tout << "found j " << j << std::endl; lp().print_column_info(j, tout)<< std::endl; + tout << "found j = " << j << std::endl;); + vector path = connect_in_tree(v, m_fixed_vertex); + explanation ex = get_explanation_from_path(path); + ex.add_expl(m_fixed_vertex_explanation); + explain_fixed_column(j, ex); + add_eq_on_columns(ex, j, v_j, true); + } + + void try_add_equation_with_val_table(const vertex *v) { + SASSERT(m_fixed_vertex); + unsigned v_j = v->column(); + const vertex *u = nullptr; + if (!m_vals_to_verts.find(val(v_j), u)) { + m_vals_to_verts.insert(val(v_j), v); + return; + } + unsigned j = u->column(); + if (j == v_j || is_int(j) != is_int(v_j)) + return; + + TRACE("cheap_eq", tout << "found j=" << j << " for v="; + print_vert(tout, v) << "\n in m_vals_to_verts\n";); + vector path = connect_in_tree(u, v); + explanation ex = get_explanation_from_path(path); + ex.add_expl(m_fixed_vertex_explanation); + add_eq_on_columns(ex, j, v_j, true); + } + + static bool not_set(unsigned j) { return j == UINT_MAX; } + static bool is_set(unsigned j) { return j != UINT_MAX; } + + void create_root(unsigned row_index) { + SASSERT(!m_root && !m_fixed_vertex); + unsigned x, y; + int polarity; + TRACE("cheap_eq_det", print_row(tout, row_index);); + if (!is_tree_offset_row(row_index, x, y, polarity)) { + TRACE("cheap_eq_det", tout << "not an offset row\n";); + return; + } + TRACE("cheap_eq", print_row(tout, row_index);); + m_root = alloc_v(x); + set_polarity(m_root, 1); // keep m_root in the positive table + if (not_set(y)) { + set_fixed_vertex(m_root); + explain_fixed_in_row(row_index, m_fixed_vertex_explanation); + } + else { + vertex *v = add_child_with_check(row_index, y, m_root, polarity); + if (v) + explore_under(v); + } + explore_under(m_root); + } + + void explore_under(vertex * v) { + check_for_eq_and_add_to_val_tables(v); + go_over_vertex_column(v); + } + + // In case of only one non fixed column, and the function returns true, + // this column would be represened by x. + bool is_tree_offset_row(unsigned row_index, unsigned & x, unsigned & y, int & polarity) const { + x = y = UINT_MAX; + const row_cell* x_cell = nullptr; + const row_cell* y_cell = nullptr; + const auto & row = lp().get_row(row_index); + for (unsigned k = 0; k < row.size(); k++) { + const auto& c = row[k]; + if (column_is_fixed(c.var())) + continue; + if (not_set(x)) { + if (c.coeff().is_one() || c.coeff().is_minus_one()) { + x = c.var(); + x_cell = & c; + } + else + return false; + } + else if (not_set(y)) { + if (c.coeff().is_one() || c.coeff().is_minus_one()) { + y = c.var(); + y_cell = & c; + } + else + return false; + } + else + return false; + } + if (is_set(x)) { + if (is_set(y)) + polarity = x_cell->coeff().is_pos() == y_cell->coeff().is_pos()? -1 : 1; + else + polarity = 1; + return true; + } + return false; + } + + void go_over_vertex_column(vertex * v) { + lpvar j = v->column(); + if (!check_insert(m_visited_columns, j)) + return; + + for (const auto & c : lp().get_column(j)) { + unsigned row_index = c.var(); + if (!check_insert(m_visited_rows, row_index)) + continue; + vertex* u = get_child_from_row(row_index, v); + if (u) + explore_under(u); + } + } + + void reset_cheap_eq_eh() { + if (!m_root) + return; + delete_tree(m_root); + m_root = nullptr; + set_fixed_vertex(nullptr); + m_fixed_vertex_explanation.clear(); + m_vals_to_verts.reset(); + m_vals_to_verts_neg.reset(); + m_pol.reset(); + m_vertices.reset(); + } + + struct reset_cheap_eq { + lp_bound_propagator& p; + reset_cheap_eq(lp_bound_propagator& p):p(p) {} + ~reset_cheap_eq() { p.reset_cheap_eq_eh(); } + }; + + public: + + lp_bound_propagator(T& imp): + m_imp(imp) {} + const vector& ibounds() const { return m_ibounds; } + void init() { m_improved_upper_bounds.clear(); m_improved_lower_bounds.clear(); m_ibounds.reset(); } - lp_bound_propagator(T& imp): m_root(nullptr), - m_fixed_vertex(nullptr), - m_imp(imp) {} - + const lar_solver& lp() const { return m_imp.lp(); } lar_solver& lp() { return m_imp.lp(); } + column_type get_column_type(unsigned j) const { return m_imp.lp().get_column_type(j); } @@ -133,9 +314,8 @@ public: } const mpq & get_lower_bound_rational(unsigned j) const { - return m_imp.lp().get_lower_bound(j).x; + return m_imp.lp().get_lower_bound(j).x; } - const impq & get_upper_bound(unsigned j) const { return m_imp.lp().get_upper_bound(j); @@ -167,19 +347,22 @@ public: found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); TRACE("try_add_bound", m_imp.lp().print_implied_bound(found_bound, tout);); } - } else { + } + else { m_improved_lower_bounds[j] = m_ibounds.size(); m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); TRACE("try_add_bound", m_imp.lp().print_implied_bound(m_ibounds.back(), tout);); } - } else { // the upper bound case + } + else { // the upper bound case if (try_get_value(m_improved_upper_bounds, j, k)) { auto & found_bound = m_ibounds[k]; if (v < found_bound.m_bound || (v == found_bound.m_bound && !found_bound.m_strict && strict)) { found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); TRACE("try_add_bound", m_imp.lp().print_implied_bound(found_bound, tout);); } - } else { + } + else { m_improved_upper_bounds[j] = m_ibounds.size(); m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); TRACE("try_add_bound", m_imp.lp().print_implied_bound(m_ibounds.back(), tout);); @@ -199,54 +382,12 @@ public: return val(v->column()); } - void try_add_equation_with_lp_fixed_tables(const vertex *v) { - SASSERT(m_fixed_vertex); - unsigned v_j = v->column(); - unsigned j = null_lpvar; - if (!lp().find_in_fixed_tables(val(v_j), is_int(v_j), j)) - return; - - TRACE("cheap_eq", tout << "v_j = "; lp().print_column_info(v_j, tout) << std::endl;); - TRACE("cheap_eq", tout << "v = "; print_vert(tout, v) << std::endl;); - TRACE("cheap_eq", tout << "found j " << j << std::endl; - lp().print_column_info(j, tout)<< std::endl;); - TRACE("cheap_eq", tout << "found j = " << j << std::endl;); - vector path = connect_in_tree(v, m_fixed_vertex); - explanation ex = get_explanation_from_path(path); - ex.add_expl(m_fixed_vertex_explanation); - explain_fixed_column(j, ex); - add_eq_on_columns(ex, j, v->column()); - - } - - void try_add_equation_with_val_table(const vertex *v) { - SASSERT(m_fixed_vertex); - unsigned v_j = v->column(); - const vertex *u = nullptr; - if (!m_vals_to_verts.find(val(v_j), u)) { - m_vals_to_verts.insert(val(v_j), v); - return; - } - unsigned j = u->column(); - if (j == v_j || is_int(j) != is_int(v_j)) - return; - - TRACE("cheap_eq", tout << "found j=" << j << " for v="; - print_vert(tout, v) << "\n in m_vals_to_verts\n";); - vector path = connect_in_tree(u, v); - explanation ex = get_explanation_from_path(path); - ex.add_expl(m_fixed_vertex_explanation); - add_eq_on_columns(ex, j, v_j); - } - - bool tree_contains_r(vertex* root, vertex *v) const { if (*root == *v) return true; - for (auto e : root->edges()) { + for (auto e : root->edges()) if (tree_contains_r(e.target(), v)) return true; - } return false; } @@ -294,38 +435,12 @@ public: return v; } - static bool not_set(unsigned j) { return j == UINT_MAX; } - static bool is_set(unsigned j) { return j != UINT_MAX; } - - void create_root(unsigned row_index) { - SASSERT(!m_root && !m_fixed_vertex); - unsigned x, y; - int polarity; - TRACE("cheap_eq_det", print_row(tout, row_index);); - if (!is_tree_offset_row(row_index, x, y, polarity)) { - TRACE("cheap_eq_det", tout << "not an offset row\n";); - return; - } - TRACE("cheap_eq", print_row(tout, row_index);); - m_root = alloc_v(x); - set_polarity(m_root, 1); // keep m_root in the positive table - if (not_set(y)) { - set_fixed_vertex(m_root); - explain_fixed_in_row(row_index, m_fixed_vertex_explanation); - } else { - vertex *v = add_child_with_check(row_index, y, m_root, polarity); - if (v) - explore_under(v); - } - explore_under(m_root); - } unsigned column(unsigned row, unsigned index) { return lp().get_row(row)[index].var(); } bool fixed_phase() const { return m_fixed_vertex; } - // Returns the vertex to start exploration from, or nullptr. @@ -379,10 +494,12 @@ public: is_int(k->column()) == is_int(v->column()) && !is_equal(k->column(), v->column())) { report_eq(k, v); - } else { + } + else { TRACE("cheap_eq", tout << "no report\n";); } - } else { + } + else { TRACE("cheap_eq", tout << "registered: " << val(v) << " -> { "; print_vert(tout, v) << "} \n";); table.insert(val(v), v); } @@ -411,37 +528,31 @@ public: std::ostream& print_path(const vector& path, std::ostream& out) const { out << "path = \n"; - for (const edge& k : path) { + for (const edge& k : path) print_edge(k, out) << "\n"; - } return out; } - - - // we have v_i and v_j, indices of vertices at the same offsets void report_eq(const vertex* v_i, const vertex* v_j) { SASSERT(v_i != v_j); SASSERT(lp().get_column_value(v_i->column()) == lp().get_column_value(v_j->column())); TRACE("cheap_eq", tout << v_i->column() << " = " << v_j->column() << "\nu = "; - print_vert(tout, v_i) << "\nv = "; print_vert(tout, v_j) <<"\n"; - ); + print_vert(tout, v_i) << "\nv = "; print_vert(tout, v_j) <<"\n"); vector path = connect_in_tree(v_i, v_j); lp::explanation exp = get_explanation_from_path(path); - add_eq_on_columns(exp, v_i->column(), v_j->column()); + add_eq_on_columns(exp, v_i->column(), v_j->column(), false); } std::ostream& print_expl(std::ostream & out, const explanation& exp) const { - for (auto p : exp) { + for (auto p : exp) lp().constraints().display(out, [this](lpvar j) { return lp().get_variable_name(j);}, p.ci()); - } return out; } - void add_eq_on_columns(const explanation& exp, lpvar j, lpvar k) { + bool add_eq_on_columns(const explanation& exp, lpvar j, lpvar k, bool is_fixed) { SASSERT(j != k); unsigned je = lp().column_to_reported_index(j); unsigned ke = lp().column_to_reported_index(k); @@ -452,8 +563,10 @@ public: tout << "theory_vars v" << lp().local_to_external(je) << " == v" << lp().local_to_external(ke) << "\n"; ); - m_imp.add_eq(je, ke, exp); - lp().settings().stats().m_cheap_eqs++; + bool added = m_imp.add_eq(je, ke, exp, is_fixed); + if (added) + lp().stats().m_offset_eqs++; + return added; } // column to theory_var @@ -478,14 +591,10 @@ public: } void explain_fixed_in_row(unsigned row, explanation& ex) const { - TRACE("cheap_eq", - tout << lp().get_row(row) << std::endl; - ); - for (const auto & c : lp().get_row(row)) { - if (lp().is_fixed(c.var())) { + TRACE("cheap_eq", tout << lp().get_row(row) << std::endl); + for (const auto & c : lp().get_row(row)) + if (lp().is_fixed(c.var())) explain_fixed_column(c.var(), ex); - } - } } void explain_fixed_column(unsigned j, explanation & ex) const { @@ -536,10 +645,9 @@ public: if (visited_verts.find(v->column()) != visited_verts.end()) return false; visited_verts.insert(v->column()); - for (auto e : v->edges()) { + for (auto e : v->edges()) if (!tree_is_correct(e.target(), visited_verts)) return false; - } return true; } std::ostream& print_tree(std::ostream & out, vertex* v) const { @@ -553,43 +661,37 @@ public: return out; } - void try_add_equation_with_fixed_tables(const vertex* v) { - try_add_equation_with_lp_fixed_tables(v); + void try_add_equation_with_fixed_tables(unsigned row_index, const vertex* v) { + try_add_equation_with_lp_fixed_tables(row_index, v); try_add_equation_with_val_table(v); } - void create_fixed_eqs(const vertex* v) { - try_add_equation_with_fixed_tables(v); + void handle_fixed_phase(unsigned row_index) { + if (!fixed_phase()) + return; + const vertex* v = m_root; + try_add_equation_with_fixed_tables(row_index, v); for (auto e: v->edges()) - try_add_equation_with_fixed_tables(e.target()); + try_add_equation_with_fixed_tables(row_index, e.target()); } - void handle_fixed_phase() { - create_fixed_eqs(m_root); - } void cheap_eq_tree(unsigned row_index) { - TRACE("cheap_eq_det", tout << "row_index = " << row_index << "\n";); - if (!check_insert(m_visited_rows, row_index)) - return; // already explored - create_root(row_index); - if (m_root == nullptr) { + reset_cheap_eq _reset(*this); + TRACE("cheap_eq_det", tout << "row_index = " << row_index << "\n";); + if (!check_insert(m_visited_rows, row_index)) return; - } - TRACE("cheap_eq", tout << "tree = "; print_tree(tout, m_root) << "\n";); + create_root(row_index); + if (!m_root) + return; + + TRACE("cheap_eq", tout << "tree = "; print_tree(tout, m_root) << "\n";); SASSERT(tree_is_correct()); - if (fixed_phase()) - handle_fixed_phase(); - TRACE("cheap_eq", tout << "done for row_index " << row_index << "\n";); - TRACE("cheap_eq", tout << "tree size = " << verts_size();); - delete_tree(m_root); - m_root = nullptr; - set_fixed_vertex(nullptr); - m_fixed_vertex_explanation.clear(); - m_vals_to_verts.reset(); - m_vals_to_verts_neg.reset(); - m_pol.reset(); - m_vertices.reset(); + handle_fixed_phase(row_index); + + TRACE("cheap_eq", + tout << "done for row_index " << row_index << "\n"; + tout << "tree size = " << verts_size();); } std::ostream& print_row(std::ostream & out, unsigned row_index) const { @@ -643,71 +745,7 @@ public: return false; table.insert(j); return true; - } - - void go_over_vertex_column(vertex * v) { - lpvar j = v->column(); - if (!check_insert(m_visited_columns, j)) - return; - - for (const auto & c : lp().get_column(j)) { - unsigned row_index = c.var(); - if (!check_insert(m_visited_rows, row_index)) - continue; - vertex *u = get_child_from_row(row_index, v); - if (u) { - // debug - // if (verts_size() > 3) { - // std::cout << "big tree\n"; - // TRACE("cheap_eq", print_tree(tout, m_root);); - // exit(1); - // } // end debug - explore_under(u); - } - } - } - - void explore_under(vertex * v) { - check_for_eq_and_add_to_val_tables(v); - go_over_vertex_column(v); - } + } - // In case of only one non fixed column, and the function returns true, - // this column would be represened by x. - bool is_tree_offset_row( unsigned row_index, - unsigned & x, unsigned & y, int & polarity ) const { - x = y = UINT_MAX; - const row_cell* x_cell = nullptr; - const row_cell* y_cell = nullptr; - const auto & row = lp().get_row(row_index); - for (unsigned k = 0; k < row.size(); k++) { - const auto& c = row[k]; - if (column_is_fixed(c.var())) - continue; - if (not_set(x)) { - if (c.coeff().is_one() || c.coeff().is_minus_one()) { - x = c.var(); - x_cell = & c; - } else { - return false; - } - } else if (not_set(y)) { - if (c.coeff().is_one() || c.coeff().is_minus_one()) { - y = c.var(); - y_cell = & c; - } else - return false; - } else - return false; - } - if (is_set(x)) { - if (is_set(y)) - polarity = x_cell->coeff().is_pos() == y_cell->coeff().is_pos()? -1 : 1; - else - polarity = 1; - return true; - } - return false; - } }; } diff --git a/src/math/lp/lp_settings.cpp b/src/math/lp/lp_settings.cpp index ce6edcfd5..592a98983 100644 --- a/src/math/lp/lp_settings.cpp +++ b/src/math/lp/lp_settings.cpp @@ -27,7 +27,7 @@ template bool lp::vectors_are_equal(vector const&, vector bool is_epsilon_small(const X & v, const double& eps); class lp_resource_limit { public: + virtual ~lp_resource_limit() = default; virtual bool get_cancel_flag() = 0; }; @@ -125,7 +126,7 @@ struct statistics { unsigned m_cross_nested_forms; unsigned m_grobner_calls; unsigned m_grobner_conflicts; - unsigned m_cheap_eqs; + unsigned m_offset_eqs; statistics() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } void collect_statistics(::statistics& st) const { @@ -146,7 +147,7 @@ struct statistics { st.update("arith-horner-cross-nested-forms", m_cross_nested_forms); st.update("arith-grobner-calls", m_grobner_calls); st.update("arith-grobner-conflicts", m_grobner_conflicts); - st.update("arith-cheap-eqs", m_cheap_eqs); + st.update("arith-offset-eqs", m_offset_eqs); } }; @@ -241,10 +242,10 @@ private: unsigned m_nlsat_delay; bool m_enable_hnf { true }; bool m_print_external_var_name { false }; - bool m_cheap_eqs { false }; + bool m_propagate_eqs { false }; public: bool print_external_var_name() const { return m_print_external_var_name; } - bool cheap_eqs() const { return m_cheap_eqs;} + bool propagate_eqs() const { return m_propagate_eqs;} unsigned hnf_cut_period() const { return m_hnf_cut_period; } void set_hnf_cut_period(unsigned period) { m_hnf_cut_period = period; } unsigned random_next() { return m_rand(); } diff --git a/src/math/polynomial/polynomial.h b/src/math/polynomial/polynomial.h index 20c9b6b40..416422f64 100644 --- a/src/math/polynomial/polynomial.h +++ b/src/math/polynomial/polynomial.h @@ -71,6 +71,7 @@ namespace polynomial { template class var2value { public: + virtual ~var2value() = default; virtual ValManager & m() const = 0; virtual bool contains(var x) const = 0; virtual Value const & operator()(var x) const = 0; @@ -100,6 +101,7 @@ namespace polynomial { struct display_var_proc { virtual std::ostream& operator()(std::ostream & out, var x) const { return out << "x" << x; } + virtual ~display_var_proc() = default; }; class polynomial; @@ -228,6 +230,7 @@ namespace polynomial { del_eh * m_next; public: del_eh():m_next(nullptr) {} + virtual ~del_eh() = default; virtual void operator()(polynomial * p) = 0; }; diff --git a/src/math/polynomial/upolynomial_factorization_int.h b/src/math/polynomial/upolynomial_factorization_int.h index e3e4793a5..e66fd2f1b 100644 --- a/src/math/polynomial/upolynomial_factorization_int.h +++ b/src/math/polynomial/upolynomial_factorization_int.h @@ -175,6 +175,8 @@ namespace upolynomial { m_current_size = 0; } + virtual ~factorization_combination_iterator_base() = default; + /** \brief Returns the factors we are enumerating through. */ diff --git a/src/math/realclosure/realclosure.h b/src/math/realclosure/realclosure.h index 8154c43d3..788db4bbf 100644 --- a/src/math/realclosure/realclosure.h +++ b/src/math/realclosure/realclosure.h @@ -37,6 +37,7 @@ namespace realclosure { class mk_interval { public: + virtual ~mk_interval() = default; virtual void operator()(unsigned k, mpqi_manager & im, mpqi_manager::interval & r) = 0; }; diff --git a/src/math/simplex/bit_matrix.cpp b/src/math/simplex/bit_matrix.cpp index fa4b481b5..097dd4396 100644 --- a/src/math/simplex/bit_matrix.cpp +++ b/src/math/simplex/bit_matrix.cpp @@ -15,6 +15,7 @@ Notes: #include "math/simplex/bit_matrix.h" #include "util/stopwatch.h" +#include "util/trace.h" #include diff --git a/src/math/subpaving/subpaving_types.h b/src/math/subpaving/subpaving_types.h index b914901a8..874013533 100644 --- a/src/math/subpaving/subpaving_types.h +++ b/src/math/subpaving/subpaving_types.h @@ -42,6 +42,7 @@ public: }; struct display_var_proc { + virtual ~display_var_proc() = default; virtual void operator()(std::ostream & out, var x) const { out << "x" << x; } }; diff --git a/src/model/array_factory.cpp b/src/model/array_factory.cpp index 6fab80661..d3ec56d39 100644 --- a/src/model/array_factory.cpp +++ b/src/model/array_factory.cpp @@ -38,7 +38,7 @@ array_factory::array_factory(ast_manager & m, model_core & md): } /** - \brieft Return as-array[f] where f is a fresh function symbol with the right domain and range for the array sort s. + \brief Return as-array[f] where f is a fresh function symbol with the right domain and range for the array sort s. Store in fi the function interpretation for f. */ expr * array_factory::mk_array_interp(sort * s, func_interp * & fi) { diff --git a/src/model/func_interp.cpp b/src/model/func_interp.cpp index 22443ee1f..1c59b6107 100644 --- a/src/model/func_interp.cpp +++ b/src/model/func_interp.cpp @@ -280,6 +280,9 @@ void func_interp::compress() { } // other compression, if else is a default branch. // or function encode identity. +#if 0 + // breaks array interpretations + // #5604 if (m().is_false(m_else)) { expr_ref new_else(get_interp(), m()); for (func_entry * curr : m_entries) { @@ -291,7 +294,9 @@ void func_interp::compress() { m().dec_ref(m_else); m_else = new_else; } - else if (!m_entries.empty() && is_identity()) { + else +#endif + if (!m_entries.empty() && is_identity()) { for (func_entry * curr : m_entries) { curr->deallocate(m(), m_arity); } @@ -335,14 +340,11 @@ expr * func_interp::get_interp_core() const { expr * r = m_else; ptr_buffer vars; for (func_entry * curr : m_entries) { - if (m_else == curr->get_result()) { + if (m_else == curr->get_result()) continue; - } - if (vars.empty()) { - for (unsigned i = 0; i < m_arity; i++) { + if (vars.empty()) + for (unsigned i = 0; i < m_arity; i++) vars.push_back(m().mk_var(i, curr->get_arg(i)->get_sort())); - } - } ptr_buffer eqs; for (unsigned i = 0; i < m_arity; i++) { eqs.push_back(m().mk_eq(vars[i], curr->get_arg(i))); diff --git a/src/model/model.cpp b/src/model/model.cpp index e93e80bfe..21938766b 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -238,15 +238,13 @@ void model::compress(bool force_inline) { top_sort ts(m); collect_deps(ts); ts.topological_sort(); - for (func_decl * f : ts.top_sorted()) { + for (func_decl * f : ts.top_sorted()) cleanup_interp(ts, f, force_inline); - } func_decl_set removed; ts.m_occur_count.reset(); - for (func_decl * f : ts.top_sorted()) { + for (func_decl * f : ts.top_sorted()) collect_occs(ts, f); - } // remove auxiliary declarations that are not used. for (func_decl * f : ts.top_sorted()) { @@ -256,7 +254,8 @@ void model::compress(bool force_inline) { removed.insert(f); } } - if (removed.empty()) break; + if (removed.empty()) + break; TRACE("model", tout << "remove\n"; for (func_decl* f : removed) tout << f->get_name() << "\n";); remove_decls(m_decls, removed); remove_decls(m_func_decls, removed); @@ -268,12 +267,14 @@ void model::compress(bool force_inline) { void model::collect_deps(top_sort& ts) { - for (auto const& kv : m_finterp) { - ts.insert(kv.m_key, collect_deps(ts, kv.m_value)); - } - for (auto const& kv : m_interp) { - ts.insert(kv.m_key, collect_deps(ts, kv.m_value.second)); - } + recfun::util u(m); + for (auto const& [f, v] : m_finterp) + if (!u.has_def(f)) + ts.insert(f, collect_deps(ts, v)); + + for (auto const& [f,v] : m_interp) + if (!u.has_def(f)) + ts.insert(f, collect_deps(ts, v.second)); } struct model::deps_collector { @@ -334,6 +335,7 @@ model::func_decl_set* model::collect_deps(top_sort& ts, func_interp * fi) { */ void model::cleanup_interp(top_sort& ts, func_decl* f, bool force_inline) { + unsigned pid = ts.partition_id(f); expr * e1 = get_const_interp(f); if (e1) { diff --git a/src/model/model_evaluator.cpp b/src/model/model_evaluator.cpp index 617cfe85f..5c6d0f311 100644 --- a/src/model/model_evaluator.cpp +++ b/src/model/model_evaluator.cpp @@ -162,7 +162,6 @@ struct evaluator_cfg : public default_rewriter_cfg { result_pr = nullptr; family_id fid = f->get_family_id(); bool _is_uninterp = fid != null_family_id && m.get_plugin(fid)->is_considered_uninterpreted(f); - func_decl* g = nullptr; br_status st = BR_FAILED; #if 0 struct pp { diff --git a/src/muz/base/dl_engine_base.h b/src/muz/base/dl_engine_base.h index 05872c06b..fcf45abf9 100644 --- a/src/muz/base/dl_engine_base.h +++ b/src/muz/base/dl_engine_base.h @@ -122,6 +122,7 @@ namespace datalog { class register_engine_base { public: + virtual ~register_engine_base() = default; virtual engine_base* mk_engine(DL_ENGINE engine_type) = 0; virtual void set_context(context* ctx) = 0; }; diff --git a/src/muz/rel/dl_mk_simple_joins.cpp b/src/muz/rel/dl_mk_simple_joins.cpp index c556e37e4..11e366f90 100644 --- a/src/muz/rel/dl_mk_simple_joins.cpp +++ b/src/muz/rel/dl_mk_simple_joins.cpp @@ -42,7 +42,7 @@ namespace datalog { /** \brief Number of rules longer than two that contain this pair. - This number is being updated by \c add_rule and \remove rule. Even though between + This number is being updated by \c add_rule and \c remove_rule. Even though between adding a rule and removing it, the length of a rule can decrease without this pair being notified about it, it will surely see the decrease from length 3 to 2 which the threshold for rule being counted in this counter. diff --git a/src/muz/spacer/spacer_mbc.h b/src/muz/spacer/spacer_mbc.h index 5c61f2d7b..19bbe839d 100644 --- a/src/muz/spacer/spacer_mbc.h +++ b/src/muz/spacer/spacer_mbc.h @@ -34,7 +34,7 @@ public: typedef obj_map partition_map; /** - \Brief Model Based Cartesian projection of lits + \brief Model Based Cartesian projection of lits */ void operator()(const partition_map &pmap, expr_ref_vector &lits, model &mdl, vector &res); diff --git a/src/nlsat/nlsat_solver.h b/src/nlsat/nlsat_solver.h index 1e3e5cdfd..c65a2b4ff 100644 --- a/src/nlsat/nlsat_solver.h +++ b/src/nlsat/nlsat_solver.h @@ -32,6 +32,7 @@ namespace nlsat { class display_assumption_proc { public: + virtual ~display_assumption_proc() = default; virtual std::ostream& operator()(std::ostream& out, assumption a) const = 0; }; diff --git a/src/opt/maxlex.cpp b/src/opt/maxlex.cpp index 9b73df8ca..46c7104d5 100644 --- a/src/opt/maxlex.cpp +++ b/src/opt/maxlex.cpp @@ -131,16 +131,18 @@ namespace opt { soft.set_value(l_undef); } model_ref mdl; - s().get_model(mdl); + s().get_model(mdl); if (mdl) { + TRACE("opt", tout << *mdl << "\n";); for (auto & soft : m_soft) { if (!mdl->is_true(soft.s)) { - break; + update_bounds(); + return; } soft.set_value(l_true); assert_value(soft); } - update_bounds(); + update_assignment(); } } @@ -151,9 +153,8 @@ namespace opt { unsigned sz = m_soft.size(); for (unsigned i = 0; i < sz; ++i) { auto& soft = m_soft[i]; - if (soft.value != l_undef) { + if (soft.value != l_undef) continue; - } expr_ref_vector asms(m); asms.push_back(soft.s); lbool is_sat = s().check_sat(asms); diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index 219b5488d..a8c0d5144 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -24,6 +24,7 @@ Notes: #include "ast/pb_decl_plugin.h" #include "ast/ast_smt_pp.h" #include "ast/ast_pp_util.h" +#include "ast/ast_ll_pp.h" #include "ast/display_dimacs.h" #include "model/model_smt2_pp.h" #include "tactic/goal.h" @@ -966,12 +967,12 @@ namespace opt { tout << "Convert minimization " << orig_term << "\n"; tout << "to maxsat: " << term << "\n"; for (unsigned i = 0; i < weights.size(); ++i) { - tout << mk_pp(terms[i].get(), m) << ": " << weights[i] << "\n"; + tout << mk_pp(terms.get(i), m) << ": " << weights[i] << "\n"; } tout << "offset: " << offset << "\n"; ); std::ostringstream out; - out << orig_term << ':' << index; + out << mk_bounded_pp(orig_term, m, 2) << ':' << index; id = symbol(out.str()); return true; } @@ -994,7 +995,7 @@ namespace opt { } neg = true; std::ostringstream out; - out << orig_term << ':' << index; + out << mk_bounded_pp(orig_term, m) << ':' << index; id = symbol(out.str()); return true; } @@ -1013,7 +1014,7 @@ namespace opt { } neg = is_max; std::ostringstream out; - out << orig_term << ':' << index; + out << mk_bounded_pp(orig_term, m, 2) << ':' << index; id = symbol(out.str()); return true; } @@ -1200,7 +1201,7 @@ namespace opt { app* context::purify(generic_model_converter_ref& fm, expr* term) { std::ostringstream out; - out << mk_pp(term, m); + out << mk_bounded_pp(term, m, 3); app* q = m.mk_fresh_const(out.str(), term->get_sort()); if (!fm) fm = alloc(generic_model_converter, m, "opt"); if (m_arith.is_int_real(term)) { diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index 43f776a68..dd717c392 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -45,6 +45,7 @@ namespace opt { class maxsat_context { public: + virtual ~maxsat_context() = default; virtual generic_model_converter& fm() = 0; // converter that removes fresh names introduced by simplification. virtual bool sat_enabled() const = 0; // is using th SAT solver core enabled? virtual solver& get_solver() = 0; // retrieve solver object (SAT or SMT solver) diff --git a/src/opt/opt_pareto.h b/src/opt/opt_pareto.h index f9c6fd912..a814ac0f8 100644 --- a/src/opt/opt_pareto.h +++ b/src/opt/opt_pareto.h @@ -26,6 +26,7 @@ namespace opt { class pareto_callback { public: + virtual ~pareto_callback() = default; virtual unsigned num_objectives() = 0; virtual expr_ref mk_gt(unsigned i, model_ref& model) = 0; virtual expr_ref mk_ge(unsigned i, model_ref& model) = 0; diff --git a/src/qe/nlarith_util.cpp b/src/qe/nlarith_util.cpp index e5c4300a5..4a4997de6 100644 --- a/src/qe/nlarith_util.cpp +++ b/src/qe/nlarith_util.cpp @@ -989,6 +989,7 @@ namespace nlarith { imp& m_imp; public: isubst(imp& i) : m_imp(i) {} + virtual ~isubst() = default; virtual void mk_lt(poly const& p, app_ref& r) = 0; virtual void mk_eq(poly const& p, app_ref& r) = 0; virtual void mk_le(poly const& p, app_ref& r) { diff --git a/src/qe/nlarith_util.h b/src/qe/nlarith_util.h index 4c6f0cd18..acbe4889e 100644 --- a/src/qe/nlarith_util.h +++ b/src/qe/nlarith_util.h @@ -96,8 +96,6 @@ namespace nlarith { bool create_branches(app* x, unsigned nl, expr* const* lits, branch_conditions& bc); /** \brief Extract non-linear variables from ground formula. - - \requires a ground formula. */ void extract_non_linear(expr* e, ptr_vector& nl_vars); diff --git a/src/qe/qe.h b/src/qe/qe.h index 6a50c0a71..a5152522f 100644 --- a/src/qe/qe.h +++ b/src/qe/qe.h @@ -34,6 +34,7 @@ namespace qe { class i_nnf_atom { public: + virtual ~i_nnf_atom() = default; virtual void operator()(expr* e, bool pol, expr_ref& result) = 0; }; diff --git a/src/sat/sat_extension.h b/src/sat/sat_extension.h index b185ebdc5..147bc90cc 100644 --- a/src/sat/sat_extension.h +++ b/src/sat/sat_extension.h @@ -40,6 +40,7 @@ namespace sat { class literal_occs_fun { public: virtual double operator()(literal l) = 0; + virtual ~literal_occs_fun() = default; }; diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index cdf3a4c68..e5fd2ef70 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -1893,7 +1893,6 @@ namespace sat { void solver::init_ext_assumptions() { if (m_ext && m_ext->tracking_assumptions()) { m_ext_assumption_set.reset(); - unsigned trail_size = m_trail.size(); if (!inconsistent()) m_ext->add_assumptions(m_ext_assumption_set); } diff --git a/src/sat/smt/arith_solver.cpp b/src/sat/smt/arith_solver.cpp index 89b778672..049bf0ce2 100644 --- a/src/sat/smt/arith_solver.cpp +++ b/src/sat/smt/arith_solver.cpp @@ -301,26 +301,27 @@ namespace arith { m_explanation.add_pair(j, v); } - void solver::add_eq(lpvar u, lpvar v, lp::explanation const& e) { + bool solver::add_eq(lpvar u, lpvar v, lp::explanation const& e, bool is_fixed) { if (s().inconsistent()) - return; + return false; theory_var uv = lp().local_to_external(u); // variables that are returned should have external representations theory_var vv = lp().local_to_external(v); // so maybe better to have them already transformed to external form if (is_equal(uv, vv)) - return; + return false; enode* n1 = var2enode(uv); enode* n2 = var2enode(vv); expr* e1 = n1->get_expr(); expr* e2 = n2->get_expr(); - if (m.is_ite(e1) || m.is_ite(e2)) - return; + if (!is_fixed && !a.is_numeral(e1) && !a.is_numeral(e2) && (m.is_ite(e1) || m.is_ite(e2))) + return false; if (e1->get_sort() != e2->get_sort()) - return; + return false; reset_evidence(); for (auto ev : e) set_evidence(ev.ci(), m_core, m_eqs); auto* jst = euf::th_explain::propagate(*this, m_core, m_eqs, n1, n2); ctx.propagate(n1, n2, jst->to_index()); + return true; } bool solver::bound_is_interesting(unsigned vi, lp::lconstraint_kind kind, const rational& bval) const { @@ -627,7 +628,7 @@ namespace arith { anum const& an = nl_value(v, *m_a1); if (a.is_int(o) && !m_nla->am().is_int(an)) value = a.mk_numeral(rational::zero(), a.is_int(o)); - value = a.mk_numeral(m_nla->am(), nl_value(v, *m_a1), a.is_int(o)); + //value = a.mk_numeral(m_nla->am(), nl_value(v, *m_a1), a.is_int(o)); } else if (v != euf::null_theory_var) { rational r = get_value(v); diff --git a/src/sat/smt/arith_solver.h b/src/sat/smt/arith_solver.h index b449e049e..759c85fae 100644 --- a/src/sat/smt/arith_solver.h +++ b/src/sat/smt/arith_solver.h @@ -450,7 +450,7 @@ namespace arith { lp::lar_solver& lp() { return *m_solver; } lp::lar_solver const& lp() const { return *m_solver; } bool is_equal(theory_var x, theory_var y) const; - void add_eq(lpvar u, lpvar v, lp::explanation const& e); + bool add_eq(lpvar u, lpvar v, lp::explanation const& e, bool is_fixed); void consume(rational const& v, lp::constraint_index j); bool bound_is_interesting(unsigned vi, lp::lconstraint_kind kind, const rational& bval) const; }; diff --git a/src/sat/smt/array_axioms.cpp b/src/sat/smt/array_axioms.cpp index 015c4a0c1..d362dacb6 100644 --- a/src/sat/smt/array_axioms.cpp +++ b/src/sat/smt/array_axioms.cpp @@ -621,8 +621,7 @@ namespace array { continue; // arrays used as indices in other arrays have to be treated as shared issue #3532, #3529 if (ctx.is_shared(r) || is_shared_arg(r)) - roots.push_back(r->get_th_var(get_id())); - + roots.push_back(r->get_th_var(get_id())); r->mark1(); to_unmark.push_back(r); } diff --git a/src/sat/smt/array_internalize.cpp b/src/sat/smt/array_internalize.cpp index 7e3f94b84..aa94f12a7 100644 --- a/src/sat/smt/array_internalize.cpp +++ b/src/sat/smt/array_internalize.cpp @@ -120,8 +120,8 @@ namespace array { SASSERT(!n || !n->is_attached_to(get_id())); if (!n) n = mk_enode(e, false); - SASSERT(!n->is_attached_to(get_id())); - mk_var(n); + if (!n->is_attached_to(get_id())) + mk_var(n); for (auto* arg : euf::enode_args(n)) ensure_var(arg); switch (a->get_decl_kind()) { diff --git a/src/sat/smt/array_model.cpp b/src/sat/smt/array_model.cpp index 33ad8e484..92a368095 100644 --- a/src/sat/smt/array_model.cpp +++ b/src/sat/smt/array_model.cpp @@ -119,7 +119,7 @@ namespace array { bool solver::must_have_different_model_values(theory_var v1, theory_var v2) { euf::enode* else1 = nullptr, * else2 = nullptr; - euf::enode* n1 = var2enode(v1), *n2 = var2enode(v2); + euf::enode* n1 = var2enode(v1); expr* e1 = n1->get_expr(); if (!a.is_array(e1)) return true; diff --git a/src/sat/smt/euf_model.cpp b/src/sat/smt/euf_model.cpp index f0f455f6c..21a02909e 100644 --- a/src/sat/smt/euf_model.cpp +++ b/src/sat/smt/euf_model.cpp @@ -46,6 +46,7 @@ namespace euf { value = r->get_expr(); else value = factory.get_fresh_value(srt); + (void)s; TRACE("model", tout << s.bpp(r) << " := " << value << "\n";); values.set(id, value); expr_ref_vector* vals = nullptr; diff --git a/src/sat/smt/euf_relevancy.cpp b/src/sat/smt/euf_relevancy.cpp index 28ac99d9a..40dff962a 100644 --- a/src/sat/smt/euf_relevancy.cpp +++ b/src/sat/smt/euf_relevancy.cpp @@ -22,6 +22,32 @@ Author: namespace euf { + void solver::add_auto_relevant(expr* e) { + if (!relevancy_enabled()) + return; + for (; m_auto_relevant_scopes > 0; --m_auto_relevant_scopes) + m_auto_relevant_lim.push_back(m_auto_relevant.size()); + // std::cout << "add-auto " << e->get_id() << " " << mk_bounded_pp(e, m) << "\n"; + m_auto_relevant.push_back(e); + } + + void solver::pop_relevant(unsigned n) { + if (m_auto_relevant_scopes >= n) { + m_auto_relevant_scopes -= n; + return; + } + n -= m_auto_relevant_scopes; + m_auto_relevant_scopes = 0; + unsigned top = m_auto_relevant_lim.size() - n; + unsigned lim = m_auto_relevant_lim[top]; + m_auto_relevant_lim.shrink(top); + m_auto_relevant.shrink(lim); + } + + void solver::push_relevant() { + ++m_auto_relevant_scopes; + } + bool solver::is_relevant(expr* e) const { return m_relevant_expr_ids.get(e->get_id(), true); } @@ -31,11 +57,11 @@ namespace euf { } void solver::ensure_dual_solver() { - if (!m_dual_solver) { - m_dual_solver = alloc(sat::dual_solver, s().rlimit()); - for (unsigned i = s().num_user_scopes(); i-- > 0; ) - m_dual_solver->push(); - } + if (m_dual_solver) + return; + m_dual_solver = alloc(sat::dual_solver, s().rlimit()); + for (unsigned i = s().num_user_scopes(); i-- > 0; ) + m_dual_solver->push(); } /** @@ -65,8 +91,6 @@ namespace euf { bool solver::init_relevancy() { m_relevant_expr_ids.reset(); - bool_vector visited; - ptr_vector todo; if (!relevancy_enabled()) return true; if (!m_dual_solver) @@ -77,12 +101,21 @@ namespace euf { for (enode* n : m_egraph.nodes()) max_id = std::max(max_id, n->get_expr_id()); m_relevant_expr_ids.resize(max_id + 1, false); + ptr_vector& todo = m_relevant_todo; + bool_vector& visited = m_relevant_visited; auto const& core = m_dual_solver->core(); + todo.reset(); for (auto lit : core) { expr* e = m_bool_var2expr.get(lit.var(), nullptr); if (e) todo.push_back(e); } +#if 0 + std::cout << "init-relevant\n"; + for (expr* e : m_auto_relevant) + std::cout << "auto-relevant " << e->get_id() << " " << mk_bounded_pp(e, m) << "\n"; +#endif + todo.append(m_auto_relevant); for (unsigned i = 0; i < todo.size(); ++i) { expr* e = todo[i]; if (visited.get(e->get_id(), false)) @@ -114,6 +147,9 @@ namespace euf { todo.push_back(arg); } + for (auto * e : todo) + visited[e->get_id()] = false; + TRACE("euf", for (enode* n : m_egraph.nodes()) if (is_relevant(n)) diff --git a/src/sat/smt/euf_solver.cpp b/src/sat/smt/euf_solver.cpp index 3c3b6e884..2af8485c1 100644 --- a/src/sat/smt/euf_solver.cpp +++ b/src/sat/smt/euf_solver.cpp @@ -183,6 +183,7 @@ namespace euf { } void solver::propagate(literal lit, ext_justification_idx idx) { + add_auto_relevant(bool_var2expr(lit.var())); s().assign(lit, sat::justification::mk_ext_justification(s().scope_lvl(), idx)); } @@ -527,6 +528,7 @@ namespace euf { m_egraph.push(); if (m_dual_solver) m_dual_solver->push(); + push_relevant(); } void solver::pop(unsigned n) { @@ -536,6 +538,7 @@ namespace euf { e->pop(n); si.pop(n); m_egraph.pop(n); + pop_relevant(n); scope const & sc = m_scopes[m_scopes.size() - n]; for (unsigned i = m_var_trail.size(); i-- > sc.m_var_lim; ) { bool_var v = m_var_trail[i]; @@ -994,7 +997,7 @@ namespace euf { ::solver::push_eh_t& push_eh, ::solver::pop_eh_t& pop_eh, ::solver::fresh_eh_t& fresh_eh) { - m_user_propagator = alloc(user::solver, *this); + m_user_propagator = alloc(user_solver::solver, *this); m_user_propagator->add(ctx, push_eh, pop_eh, fresh_eh); for (unsigned i = m_scopes.size(); i-- > 0; ) m_user_propagator->push(); diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h index 6d08ba512..0df07faff 100644 --- a/src/sat/smt/euf_solver.h +++ b/src/sat/smt/euf_solver.h @@ -99,9 +99,8 @@ namespace euf { sat::lookahead* m_lookahead = nullptr; ast_manager* m_to_m; sat::sat_internalizer* m_to_si; - scoped_ptr m_ackerman; - scoped_ptr m_dual_solver; - user::solver* m_user_propagator = nullptr; + scoped_ptr m_ackerman; + user_solver::solver* m_user_propagator = nullptr; th_solver* m_qsolver = nullptr; unsigned m_generation = 0; mutable ptr_vector m_todo; @@ -182,6 +181,8 @@ namespace euf { // relevancy bool_vector m_relevant_expr_ids; + bool_vector m_relevant_visited; + ptr_vector m_relevant_todo; void ensure_dual_solver(); bool init_relevancy(); @@ -363,6 +364,11 @@ namespace euf { // relevancy bool m_relevancy = true; + scoped_ptr m_dual_solver; + ptr_vector m_auto_relevant; + unsigned_vector m_auto_relevant_lim; + unsigned m_auto_relevant_scopes = 0; + bool relevancy_enabled() const { return m_relevancy && get_config().m_relevancy_lvl > 0; } void disable_relevancy(expr* e) { IF_VERBOSE(0, verbose_stream() << "disabling relevancy " << mk_pp(e, m) << "\n"); m_relevancy = false; } void add_root(unsigned n, sat::literal const* lits); @@ -377,6 +383,9 @@ namespace euf { void track_relevancy(sat::bool_var v); bool is_relevant(expr* e) const; bool is_relevant(enode* n) const; + void add_auto_relevant(expr* e); + void pop_relevant(unsigned n); + void push_relevant(); // model construction diff --git a/src/sat/smt/pb_constraint.h b/src/sat/smt/pb_constraint.h index 076151395..d8cec6de9 100644 --- a/src/sat/smt/pb_constraint.h +++ b/src/sat/smt/pb_constraint.h @@ -52,6 +52,9 @@ namespace pb { constraint(tag_t t, unsigned id, literal l, unsigned sz, size_t osz, unsigned k): m_tag(t), m_lit(l), m_size(sz), m_obj_size(osz), m_id(id), m_k(k) { } + + virtual ~constraint() = default; + sat::ext_constraint_idx cindex() const { return sat::constraint_base::mem2base(this); } void deallocate(small_object_allocator& a) { a.deallocate(obj_size(), sat::constraint_base::mem2base_ptr(this)); } unsigned id() const { return m_id; } diff --git a/src/sat/smt/pb_solver_interface.h b/src/sat/smt/pb_solver_interface.h index 30914fc3f..87d6c1b75 100644 --- a/src/sat/smt/pb_solver_interface.h +++ b/src/sat/smt/pb_solver_interface.h @@ -35,6 +35,7 @@ namespace pb { class solver_interface { public: + virtual ~solver_interface() = default; virtual lbool value(bool_var v) const = 0; virtual lbool value(literal lit) const = 0; virtual bool is_false(literal lit) const = 0; diff --git a/src/sat/smt/q_clause.cpp b/src/sat/smt/q_clause.cpp index 097a61a14..d2b58bb2a 100644 --- a/src/sat/smt/q_clause.cpp +++ b/src/sat/smt/q_clause.cpp @@ -32,8 +32,8 @@ namespace q { << mk_bounded_pp(rhs, m, 2); } - std::ostream& binding::display(euf::solver& ctx, unsigned num_nodes, std::ostream& out) const { - for (unsigned i = 0; i < num_nodes; ++i) + std::ostream& binding::display(euf::solver& ctx, std::ostream& out) const { + for (unsigned i = 0; i < size(); ++i) out << ctx.bpp((*this)[i]) << " "; return out; } @@ -46,7 +46,7 @@ namespace q { if (!b) return out; do { - b->display(ctx, num_decls(), out) << "\n"; + b->display(ctx, out) << "\n"; b = b->next(); } while (b != m_bindings); diff --git a/src/sat/smt/q_clause.h b/src/sat/smt/q_clause.h index 66daf07ea..0c89b2ade 100644 --- a/src/sat/smt/q_clause.h +++ b/src/sat/smt/q_clause.h @@ -22,6 +22,7 @@ Author: #include "ast/euf/euf_enode.h" #include "sat/smt/euf_solver.h" + namespace q { struct lit { @@ -35,25 +36,7 @@ namespace q { std::ostream& display(std::ostream& out) const; }; - struct binding : public dll_base { - app* m_pattern; - unsigned m_max_generation; - unsigned m_min_top_generation; - unsigned m_max_top_generation; - euf::enode* m_nodes[0]; - - binding(app* pat, unsigned max_generation, unsigned min_top, unsigned max_top): - m_pattern(pat), - m_max_generation(max_generation), - m_min_top_generation(min_top), - m_max_top_generation(max_top) {} - - euf::enode* const* nodes() { return m_nodes; } - - euf::enode* operator[](unsigned i) const { return m_nodes[i]; } - - std::ostream& display(euf::solver& ctx, unsigned num_nodes, std::ostream& out) const; - }; + struct binding; struct clause { unsigned m_index; @@ -62,10 +45,10 @@ namespace q { unsigned m_watch = 0; sat::literal m_literal = sat::null_literal; q::quantifier_stat* m_stat = nullptr; - binding* m_bindings = nullptr; + binding* m_bindings = nullptr; - clause(ast_manager& m, unsigned idx): m_index(idx), m_q(m) {} + clause(ast_manager& m, unsigned idx) : m_index(idx), m_q(m) {} std::ostream& display(euf::solver& ctx, std::ostream& out) const; lit const& operator[](unsigned i) const { return m_lits[i]; } @@ -76,6 +59,70 @@ namespace q { quantifier* q() const { return m_q; } }; + + struct binding : public dll_base { + clause* c; + app* m_pattern; + unsigned m_max_generation; + unsigned m_min_top_generation; + unsigned m_max_top_generation; + euf::enode* m_nodes[0]; + + binding(clause& c, app* pat, unsigned max_generation, unsigned min_top, unsigned max_top): + c(&c), + m_pattern(pat), + m_max_generation(max_generation), + m_min_top_generation(min_top), + m_max_top_generation(max_top) {} + + euf::enode* const* nodes() { return m_nodes; } + + euf::enode* operator[](unsigned i) const { return m_nodes[i]; } + + std::ostream& display(euf::solver& ctx, std::ostream& out) const; + + unsigned size() const { return c->num_decls(); } + + quantifier* q() const { return c->m_q; } + + bool eq(binding const& other) const { + if (q() != other.q()) + return false; + for (unsigned i = size(); i-- > 0; ) + if ((*this)[i] != other[i]) + return false; + return true; + } + }; + + struct binding_khasher { + unsigned operator()(binding const* f) const { return f->q()->get_id(); } + }; + + struct binding_chasher { + unsigned operator()(binding const* f, unsigned idx) const { return f->m_nodes[idx]->hash(); } + }; + + struct binding_hash_proc { + unsigned operator()(binding const* f) const { + return get_composite_hash(const_cast(f), f->size()); + } + }; + + struct binding_eq_proc { + bool operator()(binding const* a, binding const* b) const { return a->eq(*b); } + }; + + typedef ptr_hashtable bindings; + + inline std::ostream& operator<<(std::ostream& out, binding const& f) { + out << "[fp " << f.q()->get_id() << ":"; + for (unsigned i = 0; i < f.size(); ++i) + out << " " << f[i]->get_expr_id(); + return out << "]"; + } + + struct justification { expr* m_lhs, *m_rhs; bool m_sign; diff --git a/src/sat/smt/q_ematch.cpp b/src/sat/smt/q_ematch.cpp index 2d8ac8afd..e4e01b964 100644 --- a/src/sat/smt/q_ematch.cpp +++ b/src/sat/smt/q_ematch.cpp @@ -54,7 +54,11 @@ namespace q { m_eval(ctx), m_qstat_gen(m, ctx.get_region()), m_inst_queue(*this, ctx), - m_infer_patterns(m, ctx.get_config()) + m_infer_patterns(m, ctx.get_config()), + m_new_defs(m), + m_new_proofs(m), + m_dn(m), + m_nnf(m, m_dn) { std::function _on_merge = [&](euf::enode* root, euf::enode* other) { @@ -80,7 +84,7 @@ namespace q { unsigned num_patterns = q->get_num_patterns(); for (unsigned i = 0; i < num_patterns; i++) ensure_ground_enodes(q->get_pattern(i)); - for (auto lit : c.m_lits) { + for (auto const& lit : c.m_lits) { ensure_ground_enodes(lit.lhs); ensure_ground_enodes(lit.rhs); } @@ -105,6 +109,20 @@ namespace q { m_eval.explain(l, justification::from_index(idx), r, probing); } + quantifier_ref ematch::nnf_skolem(quantifier* q) { + expr_ref r(m); + proof_ref p(m); + m_new_defs.reset(); + m_new_proofs.reset(); + m_nnf(q, m_new_defs, m_new_proofs, r, p); + SASSERT(is_quantifier(r)); + for (expr* d : m_new_defs) + m_qs.add_unit(m_qs.mk_literal(d)); + CTRACE("q", r != q, tout << mk_pp(q, m) << " -->\n" << r << "\n" << m_new_defs << "\n";); + return quantifier_ref(to_quantifier(r), m); + } + + std::ostream& ematch::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const { auto& j = justification::from_index(idx); auto& c = j.m_clause; @@ -186,7 +204,7 @@ namespace q { void ematch::init_watch(clause& c) { unsigned idx = c.index(); - for (auto lit : c.m_lits) { + for (auto const& lit : c.m_lits) { if (!is_ground(lit.lhs)) init_watch(lit.lhs, idx); if (!is_ground(lit.rhs)) @@ -218,38 +236,71 @@ namespace q { } }; - binding* ematch::alloc_binding(unsigned n, app* pat, unsigned max_generation, unsigned min_top, unsigned max_top) { - unsigned sz = sizeof(binding) + sizeof(euf::enode* const*)*n; - void* mem = ctx.get_region().allocate(sz); - return new (mem) binding(pat, max_generation, min_top, max_top); - } + binding* ematch::tmp_binding(clause& c, app* pat, euf::enode* const* b) { + if (c.num_decls() > m_tmp_binding_capacity) { + void* mem = memory::allocate(sizeof(binding) + c.num_decls() * sizeof(euf::enode*)); + m_tmp_binding = new (mem) binding(c, pat, 0, 0, 0); + m_tmp_binding_capacity = c.num_decls(); + } - euf::enode* const* ematch::alloc_binding(clause& c, euf::enode* const* _binding) { - unsigned sz = sizeof(euf::enode* const*) * c.num_decls(); - euf::enode** binding = (euf::enode**)ctx.get_region().allocate(sz); - for (unsigned i = 0; i < c.num_decls(); ++i) - binding[i] = _binding[i]; - return binding; + for (unsigned i = c.num_decls(); i-- > 0; ) + m_tmp_binding->m_nodes[i] = b[i]; + m_tmp_binding->m_pattern = pat; + m_tmp_binding->c = &c; + + return m_tmp_binding.get(); } - void ematch::add_binding(clause& c, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_top, unsigned max_top) { + binding* ematch::alloc_binding(clause& c, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_top, unsigned max_top) { + + binding* b = tmp_binding(c, pat, _binding); + + if (m_bindings.contains(b)) + return nullptr; + + for (unsigned i = c.num_decls(); i-- > 0; ) + b->m_nodes[i] = b->m_nodes[i]->get_root(); + + if (m_bindings.contains(b)) + return nullptr; + unsigned n = c.num_decls(); - binding* b = alloc_binding(n, pat, max_generation, min_top, max_top); + unsigned sz = sizeof(binding) + sizeof(euf::enode* const*) * n; + void* mem = ctx.get_region().allocate(sz); + b = new (mem) binding(c, pat, max_generation, min_top, max_top); b->init(b); for (unsigned i = 0; i < n; ++i) - b->m_nodes[i] = _binding[i]; - binding::push_to_front(c.m_bindings, b); - ctx.push(remove_binding(ctx, c, b)); - ++m_stats.m_num_delayed_bindings; + b->m_nodes[i] = _binding[i]; + + m_bindings.insert(b); + ctx.push(insert_map(m_bindings, b)); + return b; + } + + euf::enode* const* ematch::copy_nodes(clause& c, euf::enode* const* nodes) { + unsigned sz = sizeof(euf::enode* const*) * c.num_decls(); + euf::enode** new_nodes = (euf::enode**)ctx.get_region().allocate(sz); + for (unsigned i = 0; i < c.num_decls(); ++i) + new_nodes[i] = nodes[i]; + return new_nodes; } void ematch::on_binding(quantifier* q, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_gen, unsigned max_gen) { - TRACE("q", tout << "on-binding " << mk_pp(q, m) << "\n";); unsigned idx = m_q2clauses[q]; clause& c = *m_clauses[idx]; bool new_propagation = false; - if (!propagate(false, _binding, max_generation, c, new_propagation)) - add_binding(c, pat, _binding, max_generation, min_gen, max_gen); + binding* b = alloc_binding(c, pat, _binding, max_generation, min_gen, max_gen); + if (!b) + return; + TRACE("q", b->display(ctx, tout << "on-binding " << mk_pp(q, m) << "\n") << "\n";); + + + if (false && propagate(false, _binding, max_generation, c, new_propagation)) + return; + + binding::push_to_front(c.m_bindings, b); + ctx.push(remove_binding(ctx, c, b)); + ++m_stats.m_num_delayed_bindings; } bool ematch::propagate(bool is_owned, euf::enode* const* binding, unsigned max_generation, clause& c, bool& propagated) { @@ -272,7 +323,7 @@ namespace q { if (ev == l_undef && max_generation > m_generation_propagation_threshold) return false; if (!is_owned) - binding = alloc_binding(c, binding); + binding = copy_nodes(c, binding); auto j_idx = mk_justification(idx, c, binding); @@ -308,17 +359,14 @@ namespace q { return true; } - void ematch::instantiate(binding& b, clause& c) { + void ematch::instantiate(binding& b) { if (m_stats.m_num_instantiations > ctx.get_config().m_qi_max_instances) return; unsigned max_generation = b.m_max_generation; - max_generation = std::max(max_generation, c.m_stat->get_generation()); - c.m_stat->update_max_generation(max_generation); - fingerprint * f = add_fingerprint(c, b, max_generation); - if (!f) - return; - m_inst_queue.insert(f); - m_stats.m_num_instantiations++; + max_generation = std::max(max_generation, b.c->m_stat->get_generation()); + b.c->m_stat->update_max_generation(max_generation); + m_stats.m_num_instantiations++; + m_inst_queue.insert(&b); } void ematch::add_instantiation(clause& c, binding& b, sat::literal lit) { @@ -326,35 +374,6 @@ namespace q { ctx.propagate(lit, mk_justification(UINT_MAX, c, b.nodes())); } - void ematch::set_tmp_binding(fingerprint& fp) { - binding& b = *fp.b; - clause& c = *fp.c; - if (c.num_decls() > m_tmp_binding_capacity) { - void* mem = memory::allocate(sizeof(binding) + c.num_decls()*sizeof(euf::enode*)); - m_tmp_binding = new (mem) binding(b.m_pattern, 0, 0, 0); - m_tmp_binding_capacity = c.num_decls(); - } - - fp.b = m_tmp_binding.get(); - for (unsigned i = c.num_decls(); i-- > 0; ) - fp.b->m_nodes[i] = b[i]; - } - - fingerprint* ematch::add_fingerprint(clause& c, binding& b, unsigned max_generation) { - fingerprint fp(c, b, max_generation); - if (m_fingerprints.contains(&fp)) - return nullptr; - set_tmp_binding(fp); - for (unsigned i = c.num_decls(); i-- > 0; ) - fp.b->m_nodes[i] = fp.b->m_nodes[i]->get_root(); - if (m_fingerprints.contains(&fp)) - return nullptr; - fingerprint* f = new (ctx.get_region()) fingerprint(c, b, max_generation); - m_fingerprints.insert(f); - ctx.push(insert_map(m_fingerprints, f)); - return f; - } - sat::literal ematch::instantiate(clause& c, euf::enode* const* binding, lit const& l) { expr_ref_vector _binding(m); for (unsigned i = 0; i < c.num_decls(); ++i) @@ -427,7 +446,10 @@ namespace q { cl->m_literal.neg(); expr_ref body(mk_not(m, q->get_expr()), m); q = m.update_quantifier(q, forall_k, body); - } + } + q = nnf_skolem(q); + + expr_ref_vector ors(m); flatten_or(q->get_expr(), ors); for (expr* arg : ors) @@ -547,11 +569,16 @@ namespace q { } + bool ematch::unit_propagate() { + return false; + return ctx.get_config().m_ematching && propagate(false); + } + bool ematch::propagate(bool flush) { m_mam->propagate(); bool propagated = flush_prop_queue(); if (m_qhead >= m_clause_queue.size()) - return m_inst_queue.propagate(); + return m_inst_queue.propagate() || propagated; ctx.push(value_trail(m_qhead)); ptr_buffer to_remove; for (; m_qhead < m_clause_queue.size(); ++m_qhead) { @@ -561,12 +588,13 @@ namespace q { if (!b) continue; - do { - if (propagate(true, b->m_nodes, b->m_max_generation, c, propagated)) + do { + if (false && propagate(true, b->m_nodes, b->m_max_generation, c, propagated)) to_remove.push_back(b); else if (flush) { - instantiate(*b, c); + instantiate(*b); to_remove.push_back(b); + propagated = true; } b = b->next(); } @@ -592,21 +620,22 @@ namespace q { TRACE("q", m_mam->display(tout);); if (propagate(false)) return true; - if (m_lazy_mam) { + if (m_lazy_mam) m_lazy_mam->propagate(); - if (propagate(false)) - return true; - } - unsigned idx = 0; - for (clause* c : m_clauses) { - if (c->m_bindings) - insert_clause_in_queue(idx); - idx++; - } + if (propagate(false)) + return true; + for (unsigned i = 0; i < m_clauses.size(); ++i) + if (m_clauses[i]->m_bindings) + insert_clause_in_queue(i); if (propagate(true)) return true; if (m_inst_queue.lazy_propagate()) return true; + for (unsigned i = 0; i < m_clauses.size(); ++i) + if (m_clauses[i]->m_bindings) + IF_VERBOSE(0, verbose_stream() << "missed propagation " << i << "\n"); + + TRACE("q", tout << "no more propagation\n";); return false; } diff --git a/src/sat/smt/q_ematch.h b/src/sat/smt/q_ematch.h index 41e844572..3cdcfc80e 100644 --- a/src/sat/smt/q_ematch.h +++ b/src/sat/smt/q_ematch.h @@ -19,11 +19,11 @@ Author: #include "util/nat_set.h" #include "ast/quantifier_stat.h" #include "ast/pattern/pattern_inference.h" +#include "ast/normal_forms/nnf.h" #include "solver/solver.h" #include "sat/smt/sat_th.h" #include "sat/smt/q_mam.h" #include "sat/smt/q_clause.h" -#include "sat/smt/q_fingerprint.h" #include "sat/smt/q_queue.h" #include "sat/smt/q_eval.h" @@ -69,7 +69,7 @@ namespace q { ast_manager& m; eval m_eval; quantifier_stat_gen m_qstat_gen; - fingerprints m_fingerprints; + bindings m_bindings; scoped_ptr m_tmp_binding; unsigned m_tmp_binding_capacity = 0; queue m_inst_queue; @@ -90,16 +90,16 @@ namespace q { unsigned_vector m_clause_queue; euf::enode_pair_vector m_evidence; - binding* alloc_binding(unsigned n, app* pat, unsigned max_generation, unsigned min_top, unsigned max_top); - euf::enode* const* alloc_binding(clause& c, euf::enode* const* _binding); - void add_binding(clause& c, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_top, unsigned max_top); - + euf::enode* const* copy_nodes(clause& c, euf::enode* const* _binding); + binding* tmp_binding(clause& c, app* pat, euf::enode* const* _binding); + binding* alloc_binding(clause& c, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_top, unsigned max_top); + sat::ext_justification_idx mk_justification(unsigned idx, clause& c, euf::enode* const* b); void ensure_ground_enodes(expr* e); void ensure_ground_enodes(clause const& c); - void instantiate(binding& b, clause& c); + void instantiate(binding& b); sat::literal instantiate(clause& c, euf::enode* const* binding, lit const& l); // register as callback into egraph. @@ -115,21 +115,26 @@ namespace q { clause* clausify(quantifier* q); lit clausify_literal(expr* arg); - fingerprint* add_fingerprint(clause& c, binding& b, unsigned max_generation); - void set_tmp_binding(fingerprint& fp); - bool flush_prop_queue(); void propagate(bool is_conflict, unsigned idx, sat::ext_justification_idx j_idx); + bool propagate(bool flush); + + expr_ref_vector m_new_defs; + proof_ref_vector m_new_proofs; + defined_names m_dn; + nnf m_nnf; + + quantifier_ref nnf_skolem(quantifier* q); + public: ematch(euf::solver& ctx, solver& s); bool operator()(); - bool propagate(bool flush); + bool unit_propagate(); - // void init_search(); void add(quantifier* q); diff --git a/src/sat/smt/q_fingerprint.h b/src/sat/smt/q_fingerprint.h deleted file mode 100644 index 99ad602b9..000000000 --- a/src/sat/smt/q_fingerprint.h +++ /dev/null @@ -1,77 +0,0 @@ -/*++ -Copyright (c) 2020 Microsoft Corporation - -Module Name: - - q_fingerprint.h - -Abstract: - - Fingerprint summary of a quantifier instantiation - -Author: - - Nikolaj Bjorner (nbjorner) 2021-01-24 - ---*/ -#pragma once - -#include "util/hashtable.h" -#include "ast/ast.h" -#include "ast/quantifier_stat.h" -#include "ast/euf/euf_enode.h" -#include "sat/smt/q_clause.h" - - -namespace q { - - struct fingerprint { - clause* c; - binding* b; - unsigned m_max_generation; - - unsigned size() const { return c->num_decls(); } - euf::enode* const* nodes() const { return b->nodes(); } - quantifier* q() const { return c->m_q; } - - fingerprint(clause& _c, binding& _b, unsigned mg) : - c(&_c), b(&_b), m_max_generation(mg) {} - - bool eq(fingerprint const& other) const { - if (c->m_q != other.c->m_q) - return false; - for (unsigned i = size(); i--> 0; ) - if ((*b)[i] != (*other.b)[i]) - return false; - return true; - } - }; - - struct fingerprint_khasher { - unsigned operator()(fingerprint const * f) const { return f->c->m_q->get_id(); } - }; - - struct fingerprint_chasher { - unsigned operator()(fingerprint const * f, unsigned idx) const { return f->b->m_nodes[idx]->hash(); } - }; - - struct fingerprint_hash_proc { - unsigned operator()(fingerprint const * f) const { - return get_composite_hash(const_cast(f), f->size()); - } - }; - - struct fingerprint_eq_proc { - bool operator()(fingerprint const* a, fingerprint const* b) const { return a->eq(*b); } - }; - - typedef ptr_hashtable fingerprints; - - inline std::ostream& operator<<(std::ostream& out, fingerprint const& f) { - out << "[fp " << f.q()->get_id() << ":"; - for (unsigned i = 0; i < f.size(); ++i) - out << " " << (*f.b)[i]->get_expr_id(); - return out << "]"; - } - -} diff --git a/src/sat/smt/q_mam.cpp b/src/sat/smt/q_mam.cpp index 7999ecea9..a968314c2 100644 --- a/src/sat/smt/q_mam.cpp +++ b/src/sat/smt/q_mam.cpp @@ -409,16 +409,17 @@ namespace q { unsigned m_num_args; //!< we need this information to avoid the nary *,+ crash bug bool m_filter_candidates; unsigned m_num_regs; - unsigned m_num_choices; - instruction * m_root; + unsigned m_num_choices = 0; + instruction * m_root = nullptr; enode_vector m_candidates; + unsigned m_qhead = 0; #ifdef Z3DEBUG - egraph * m_egraph; + egraph * m_egraph = nullptr; svector> m_patterns; #endif #ifdef _PROFILE_MAM stopwatch m_watch; - unsigned m_counter; + unsigned m_counter = 0; #endif friend class compiler; friend class code_tree_manager; @@ -492,13 +493,7 @@ namespace q { m_root_lbl(lbl), m_num_args(num_args), m_filter_candidates(filter_candidates), - m_num_regs(num_args + 1), - m_num_choices(0), - m_root(nullptr) { - DEBUG_CODE(m_egraph = nullptr;); -#ifdef _PROFILE_MAM - m_counter = 0; -#endif + m_num_regs(num_args + 1) { (void)m_lbl_hasher; } @@ -546,16 +541,40 @@ namespace q { return m_root; } - void add_candidate(enode * n) { + void add_candidate(euf::solver& ctx, enode * n) { m_candidates.push_back(n); + ctx.push(push_back_trail(m_candidates)); } + void unmark(unsigned head) { + for (unsigned i = m_candidates.size(); i-- > head; ) { + enode* app = m_candidates[i]; + if (app->is_marked1()) + app->unmark1(); + } + } + + struct scoped_unmark { + unsigned m_qhead; + code_tree* t; + scoped_unmark(code_tree* t) : m_qhead(t->m_qhead), t(t) {} + ~scoped_unmark() { t->unmark(m_qhead); } + }; + + bool has_candidates() const { - return !m_candidates.empty(); + return m_qhead < m_candidates.size(); } - void reset_candidates() { - m_candidates.reset(); + void save_qhead(euf::solver& ctx) { + ctx.push(value_trail(m_qhead)); + } + + enode* next_candidate() { + if (m_qhead < m_candidates.size()) + return m_candidates[m_qhead++]; + else + return nullptr; } enode_vector const & get_candidates() const { @@ -1987,33 +2006,29 @@ namespace q { m_backtrack_stack.resize(t->get_num_choices()); } + void execute(code_tree * t) { + if (!t->has_candidates()) + return; TRACE("trigger_bug", tout << "execute for code tree:\n"; t->display(tout);); init(t); - if (t->filter_candidates()) { - for (unsigned i = 0; i < t->get_candidates().size(); ++i) { - enode* app = t->get_candidates()[i]; - TRACE("trigger_bug", tout << "candidate\n" << mk_ismt2_pp(app->get_expr(), m) << "\n";); + t->save_qhead(ctx); + enode* app; + if (t->filter_candidates()) { + code_tree::scoped_unmark _unmark(t); + while ((app = t->next_candidate()) && !ctx.resource_limits_exceeded()) { + TRACE("trigger_bug", tout << "candidate\n" << ctx.bpp(app) << "\n";); if (!app->is_marked1() && app->is_cgr()) { - if (ctx.resource_limits_exceeded() || !execute_core(t, app)) - return; + execute_core(t, app); app->mark1(); } } - for (enode* app : t->get_candidates()) { - if (app->is_marked1()) - app->unmark1(); - } } else { - for (unsigned i = 0; i < t->get_candidates().size(); ++i) { - enode* app = t->get_candidates()[i]; - TRACE("trigger_bug", tout << "candidate\n" << mk_ismt2_pp(app->get_expr(), m) << "\n";); - if (app->is_cgr()) { - TRACE("trigger_bug", tout << "is_cgr\n";); - if (ctx.resource_limits_exceeded() || !execute_core(t, app)) - return; - } + while ((app = t->next_candidate()) && !ctx.resource_limits_exceeded()) { + TRACE("trigger_bug", tout << "candidate\n" << ctx.bpp(app) << "\n";); + if (app->is_cgr()) + execute_core(t, app); } } } @@ -2474,17 +2489,18 @@ namespace q { case YIELD1: m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[0]]; -#define ON_MATCH(NUM) \ +#define ON_MATCH(NUM) \ m_max_generation = std::max(m_max_generation, get_max_generation(NUM, m_bindings.begin())); \ - if (!m.inc()) { \ - return false; \ - } \ + if (!m.inc()) \ + return false; \ + \ m_mam.on_match(static_cast(m_pc)->m_qa, \ static_cast(m_pc)->m_pat, \ NUM, \ m_bindings.begin(), \ m_max_generation) + SASSERT(static_cast(m_pc)->m_qa->get_decl_sort(0) == m_bindings[0]->get_expr()->get_sort()); ON_MATCH(1); goto backtrack; @@ -3056,29 +3072,9 @@ namespace q { // temporary field used to collect candidates ptr_vector m_todo; - enode * m_root { nullptr }; // temp field - enode * m_other { nullptr }; // temp field - bool m_check_missing_instances { false }; - - class pop_to_match : public trail { - mam_impl& i; - public: - pop_to_match(mam_impl& i):i(i) {} - void undo() override { - code_tree* t = i.m_to_match.back(); - t->reset_candidates(); - i.m_to_match.pop_back(); - } - }; - - class reset_new_patterns : public trail { - mam_impl& i; - public: - reset_new_patterns(mam_impl& i):i(i) {} - void undo() override { - i.m_new_patterns.reset(); - } - }; + enode * m_root = nullptr; // temp field + enode * m_other = nullptr; // temp field + bool m_check_missing_instances = false; enode_vector * mk_tmp_vector() { enode_vector * r = m_pool.mk(); @@ -3091,14 +3087,14 @@ namespace q { } void add_candidate(code_tree * t, enode * app) { - if (t) { - TRACE("mam_candidate", tout << "adding candidate:\n" << mk_ll_pp(app->get_expr(), m);); - if (!t->has_candidates()) { - ctx.push(pop_to_match(*this)); - m_to_match.push_back(t); - } - t->add_candidate(app); + if (!t) + return; + TRACE("q", tout << "candidate " << t << " " << ctx.bpp(app) << "\n";); + if (!t->has_candidates()) { + ctx.push(push_back_trail(m_to_match)); + m_to_match.push_back(t); } + t->add_candidate(ctx, app); } void add_candidate(enode * app) { @@ -3675,14 +3671,20 @@ namespace q { } } - void match_new_patterns() { + unsigned m_new_patterns_qhead = 0; + + void propagate_new_patterns() { + if (m_new_patterns_qhead >= m_new_patterns.size()) + return; + ctx.push(value_trail(m_new_patterns_qhead)); + TRACE("mam_new_pat", tout << "matching new patterns:\n";); m_tmp_trees_to_delete.reset(); - for (auto const& kv : m_new_patterns) { + for (; m_new_patterns_qhead < m_new_patterns.size(); ++m_new_patterns_qhead) { if (!m.inc()) break; - quantifier * qa = kv.first; - app * mp = kv.second; + auto [qa, mp] = m_new_patterns[m_new_patterns_qhead]; + SASSERT(m.is_pattern(mp)); app * p = to_app(mp->get_arg(0)); func_decl * lbl = p->get_decl(); @@ -3714,7 +3716,6 @@ namespace q { m_tmp_trees[lbl_id] = nullptr; dealloc(tmp_tree); } - m_new_patterns.reset(); } public: @@ -3750,7 +3751,7 @@ namespace q { return; // ignore multi-pattern containing ground pattern. update_filters(qa, mp); m_new_patterns.push_back(qp_pair(qa, mp)); - ctx.push(reset_new_patterns(*this)); + ctx.push(push_back_trail(m_new_patterns)); // The matching abstract machine implements incremental // e-matching. So, for a multi-pattern [ p_1, ..., p_n ], // we have to make n insertions. In the i-th insertion, @@ -3761,7 +3762,6 @@ namespace q { void reset() override { m_trees.reset(); - m_new_patterns.reset(); m_is_plbl.reset(); m_is_clbl.reset(); reset_pp_pc(); @@ -3776,22 +3776,18 @@ namespace q { return out; } - void propagate() override { - TRACE("trigger_bug", tout << "match\n"; display(tout);); - if (m_to_match_head >= m_to_match.size()) + void propagate_to_match() { + if (m_to_match_head >= m_to_match.size()) return; ctx.push(value_trail(m_to_match_head)); - for (; m_to_match_head < m_to_match.size(); ++m_to_match_head) { - code_tree* t = m_to_match[m_to_match_head]; - if (t->has_candidates()) { - m_interpreter.execute(t); - t->reset_candidates(); - } - } - if (!m_new_patterns.empty()) { - match_new_patterns(); - m_new_patterns.reset(); - } + for (; m_to_match_head < m_to_match.size(); ++m_to_match_head) + m_interpreter.execute(m_to_match[m_to_match_head]); + } + + void propagate() override { + TRACE("trigger_bug", tout << "match\n"; display(tout);); + propagate_to_match(); + propagate_new_patterns(); } void rematch(bool use_irrelevant) override { diff --git a/src/sat/smt/q_queue.cpp b/src/sat/smt/q_queue.cpp index 247451fb4..d74ee4e39 100644 --- a/src/sat/smt/q_queue.cpp +++ b/src/sat/smt/q_queue.cpp @@ -86,13 +86,13 @@ namespace q { m_parser.add_var("cs_factor"); } - void queue::set_values(fingerprint& f, float cost) { + void queue::set_values(binding& f, float cost) { quantifier_stat * stat = f.c->m_stat; quantifier* q = f.q(); - app* pat = f.b->m_pattern; + app* pat = f.m_pattern; m_vals[COST] = cost; - m_vals[MIN_TOP_GENERATION] = static_cast(f.b->m_min_top_generation); - m_vals[MAX_TOP_GENERATION] = static_cast(f.b->m_max_top_generation); + m_vals[MIN_TOP_GENERATION] = static_cast(f.m_min_top_generation); + m_vals[MAX_TOP_GENERATION] = static_cast(f.m_max_top_generation); m_vals[INSTANCES] = static_cast(stat->get_num_instances_curr_branch()); m_vals[SIZE] = static_cast(stat->get_size()); m_vals[DEPTH] = static_cast(stat->get_depth()); @@ -108,14 +108,14 @@ namespace q { TRACE("q_detail", for (unsigned i = 0; i < m_vals.size(); i++) { tout << m_vals[i] << " "; } tout << "\n";); } - float queue::get_cost(fingerprint& f) { + float queue::get_cost(binding& f) { set_values(f, 0); float r = m_evaluator(m_cost_function, m_vals.size(), m_vals.data()); f.c->m_stat->update_max_cost(r); return r; } - unsigned queue::get_new_gen(fingerprint& f, float cost) { + unsigned queue::get_new_gen(binding& f, float cost) { set_values(f, cost); float r = m_evaluator(m_new_gen_function, m_vals.size(), m_vals.data()); return std::max(f.m_max_generation + 1, static_cast(r)); @@ -129,7 +129,7 @@ namespace q { } }; - void queue::insert(fingerprint* f) { + void queue::insert(binding* f) { float cost = get_cost(*f); if (m_new_entries.empty()) ctx.push(reset_new_entries(m_new_entries)); @@ -137,7 +137,7 @@ namespace q { } void queue::instantiate(entry& ent) { - fingerprint & f = *ent.m_qb; + binding& f = *ent.m_qb; quantifier * q = f.q(); unsigned num_bindings = f.size(); quantifier_stat * stat = f.c->m_stat; @@ -146,12 +146,18 @@ namespace q { unsigned gen = get_new_gen(f, ent.m_cost); bool new_propagation = false; - if (em.propagate(true, f.nodes(), gen, *f.c, new_propagation)) + if (false && em.propagate(true, f.nodes(), gen, *f.c, new_propagation)) return; +#if 0 + std::cout << mk_pp(q, m) << "\n"; + std::cout << num_bindings << "\n"; + for (unsigned i = 0; i < num_bindings; ++i) + std::cout << mk_pp(f[i]->get_expr(), m) << " " << mk_pp(f[i]->get_sort(), m) << "\n"; +#endif auto* ebindings = m_subst(q, num_bindings); for (unsigned i = 0; i < num_bindings; ++i) - ebindings[i] = f.nodes()[i]->get_expr(); + ebindings[i] = f[i]->get_expr(); expr_ref instance = m_subst(); ctx.get_rewriter()(instance); if (m.is_true(instance)) { @@ -161,10 +167,13 @@ namespace q { stat->inc_num_instances(); m_stats.m_num_instances++; + + // f.display(ctx, std::cout << mk_pp(f.q(), m) << "\n" << instance << "\n") << "\n"; + euf::solver::scoped_generation _sg(ctx, gen); sat::literal result_l = ctx.mk_literal(instance); - em.add_instantiation(*f.c, *f.b, result_l); + em.add_instantiation(*f.c, f, result_l); } bool queue::propagate() { @@ -178,7 +187,7 @@ namespace q { if (0 == since_last_check && ctx.resource_limits_exceeded()) break; - fingerprint& f = *curr.m_qb; + binding& f = *curr.m_qb; if (curr.m_cost <= m_eager_cost_threshold) instantiate(curr); @@ -223,15 +232,14 @@ namespace q { } } bool instantiated = false; - unsigned idx = 0; - for (entry & e : m_delayed_entries) { + for (unsigned idx = 0; idx < m_delayed_entries.size(); ++idx) { + entry & e = m_delayed_entries[idx]; if (!e.m_instantiated && e.m_cost <= cost_limit) { instantiated = true; ctx.push(reset_instantiated(*this, idx)); m_stats.m_num_lazy_instances++; instantiate(e); } - ++idx; } return instantiated; } diff --git a/src/sat/smt/q_queue.h b/src/sat/smt/q_queue.h index c23cb0377..3750ee31b 100644 --- a/src/sat/smt/q_queue.h +++ b/src/sat/smt/q_queue.h @@ -20,7 +20,7 @@ Author: #include "ast/cost_evaluator.h" #include "ast/rewriter/cached_var_subst.h" #include "parsers/util/cost_parser.h" -#include "sat/smt/q_fingerprint.h" +#include "sat/smt/q_clause.h" @@ -51,12 +51,12 @@ namespace q { cost_evaluator m_evaluator; cached_var_subst m_subst; svector m_vals; - double m_eager_cost_threshold { 0 }; + double m_eager_cost_threshold = 0; struct entry { - fingerprint * m_qb; + binding * m_qb; float m_cost; - bool m_instantiated{ false }; - entry(fingerprint * f, float c):m_qb(f), m_cost(c) {} + bool m_instantiated = false; + entry(binding * f, float c):m_qb(f), m_cost(c) {} }; struct reset_new_entries; struct reset_instantiated; @@ -64,18 +64,18 @@ namespace q { svector m_new_entries; svector m_delayed_entries; - float get_cost(fingerprint& f); - void set_values(fingerprint& f, float cost); + float get_cost(binding& f); + void set_values(binding& f, float cost); void init_parser_vars(); void setup(); - unsigned get_new_gen(fingerprint& f, float cost); + unsigned get_new_gen(binding& f, float cost); void instantiate(entry& e); public: queue(ematch& em, euf::solver& ctx); - void insert(fingerprint* f); + void insert(binding* f); bool propagate(); diff --git a/src/sat/smt/q_solver.cpp b/src/sat/smt/q_solver.cpp index 480853dbd..148383c2b 100644 --- a/src/sat/smt/q_solver.cpp +++ b/src/sat/smt/q_solver.cpp @@ -100,7 +100,7 @@ namespace q { } bool solver::unit_propagate() { - return ctx.get_config().m_ematching && m_ematch.propagate(false); + return m_ematch.unit_propagate(); } euf::theory_var solver::mk_var(euf::enode* n) { diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index bb9358e59..7e66a7156 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -52,7 +52,7 @@ namespace euf { virtual void apply_sort_cnstr(enode* n, sort* s) {} /** - \record that an equality has been internalized. + \brief Record that an equality has been internalized. */ virtual void eq_internalized(enode* n) {} diff --git a/src/sat/smt/user_solver.cpp b/src/sat/smt/user_solver.cpp index e9d8a54cb..5a1a07f11 100644 --- a/src/sat/smt/user_solver.cpp +++ b/src/sat/smt/user_solver.cpp @@ -18,7 +18,7 @@ Author: #include "sat/smt/user_solver.h" #include "sat/smt/euf_solver.h" -namespace user { +namespace user_solver { solver::solver(euf::solver& ctx) : th_euf_solver(ctx, symbol("user"), ctx.get_manager().mk_family_id("user")) diff --git a/src/sat/smt/user_solver.h b/src/sat/smt/user_solver.h index e16eb5b5e..2939a2f0a 100644 --- a/src/sat/smt/user_solver.h +++ b/src/sat/smt/user_solver.h @@ -23,7 +23,7 @@ Author: #include "solver/solver.h" -namespace user { +namespace user_solver { class solver : public euf::th_euf_solver, public ::solver::propagate_callback { diff --git a/src/smt/mam.cpp b/src/smt/mam.cpp index bc97daed2..a90403626 100644 --- a/src/smt/mam.cpp +++ b/src/smt/mam.cpp @@ -2091,7 +2091,6 @@ namespace { enode * n = m_registers[j2->m_reg]->get_root(); if (n->get_num_parents() == 0) return nullptr; - unsigned num_args = n->get_num_args(); enode_vector * v = mk_enode_vector(); enode_vector::const_iterator it1 = n->begin_parents(); enode_vector::const_iterator end1 = n->end_parents(); diff --git a/src/smt/seq_eq_solver.cpp b/src/smt/seq_eq_solver.cpp index a8743d21c..9203c35bd 100644 --- a/src/smt/seq_eq_solver.cpp +++ b/src/smt/seq_eq_solver.cpp @@ -684,11 +684,12 @@ bool theory_seq::branch_quat_variable(depeq const& e) { cond = true; } // xs and ys cannot align - else if (!can_align_from_lhs(xs, ys) && !can_align_from_rhs(xs, ys)) + else if (!can_align_from_lhs(xs, ys) && !can_align_from_rhs(xs, ys) && !can_align_from_lhs(ys, xs) && !can_align_from_rhs(ys, xs)) cond = true; if (!cond) return false; + literal_vector lits; if (xs == ys) { @@ -724,7 +725,7 @@ bool theory_seq::branch_quat_variable(depeq const& e) { } } else { - TRACE("seq", tout << mk_pp(x1, m) << " > " << mk_pp(y1, m) << "\n";); + TRACE("seq", tout << mk_pp(x1, m) << " >\n" << mk_pp(y1, m) << "\n";); if (ctx.get_assignment(lit3) == l_undef) { ctx.mark_as_relevant(lit3); return true; @@ -1144,7 +1145,7 @@ bool theory_seq::solve_nth_eq(expr_ref_vector const& ls, expr_ref_vector const& m.inc_ref(rhs); m.inc_ref(ls[0]); m_nth_eq2_cache.insert(std::make_pair(rhs, ls[0])); - ctx.push_trail(remove_obj_pair_map(m, m_nth_eq2_cache, rhs, ls[0])); + get_trail_stack().push(remove_obj_pair_map(m, m_nth_eq2_cache, rhs, ls[0])); ls1.push_back(s); if (!idx_is_zero) rs1.push_back(m_sk.mk_pre(s, idx)); rs1.push_back(m_util.str.mk_unit(rhs)); diff --git a/src/smt/seq_offset_eq.h b/src/smt/seq_offset_eq.h index e3c138f9e..3535343fd 100644 --- a/src/smt/seq_offset_eq.h +++ b/src/smt/seq_offset_eq.h @@ -42,7 +42,7 @@ namespace smt { seq_offset_eq(theory& th, ast_manager& m); bool empty() const { return m_offset_equalities.empty(); } /** - \breif determine if r1 = r2 + offset + \brief determine if r1 = r2 + offset */ bool find(enode* r1, enode* r2, int& offset) const; bool contains(enode* r1, enode* r2) const { int offset = 0; return find(r1, r2, offset); } diff --git a/src/smt/seq_regex.cpp b/src/smt/seq_regex.cpp index 80282c3f8..aa1faa131 100644 --- a/src/smt/seq_regex.cpp +++ b/src/smt/seq_regex.cpp @@ -12,6 +12,7 @@ Abstract: Author: Nikolaj Bjorner (nbjorner) 2020-5-22 + Margus Veanes 2021 --*/ @@ -253,10 +254,8 @@ namespace smt { * (accept s (i + 1) (derivative s[i] r) * * Acceptance of a derivative is unfolded into a disjunction over - * all derivatives. Effectively, this implements the following rule, - * but all in one step: - * (accept s i (ite c r1 r2)) => - * c & (accept s i r1) \/ ~c & (accept s i r2) + * all derivatives. Effectively, this implements the following rule: + * (accept s i (ite c r1 r2)) => (ite c (accept s i r1) (accept s i r2)) */ void seq_regex::propagate_accept(literal lit) { SASSERT(!lit.sign()); @@ -302,6 +301,7 @@ namespace smt { unsigned min_len = re().min_length(r); unsigned min_len_plus_i = u().max_plus(min_len, idx); literal len_s_ge_min = th.m_ax.mk_ge(th.mk_len(s), min_len_plus_i); + // Acc(s,i,r) ==> |s| >= i + minlength(r) th.propagate_lit(nullptr, 1, &lit, len_s_ge_min); // Axiom equivalent to the above: th.add_axiom(~lit, len_s_ge_min); @@ -316,6 +316,8 @@ namespace smt { STRACE("seq_regex_brief", tout << " (Warning: min_length returned 0 for" << " non-nullable regex)";); + // since nullable(r) = false: + // Acc(s,i,r) ==> |s|>i th.propagate_lit(nullptr, 1, &lit, ~len_s_le_i); } else if (!m.is_true(is_nullable)) { @@ -327,7 +329,9 @@ namespace smt { << " (Warning: is_nullable did not simplify)";); literal is_nullable_lit = th.mk_literal(is_nullable); ctx.mark_as_relevant(is_nullable_lit); + // Acc(s,i,r) & |s|<=i ==> nullable(r) th.add_axiom(~lit, ~len_s_le_i, is_nullable_lit); + //TODO: what if is_nullable contains an in_re if (str().is_in_re(is_nullable)) th.add_unhandled_expr(is_nullable); } @@ -335,14 +339,19 @@ namespace smt { // Rule 3: derivative unfolding literal_vector accept_next; - expr_ref hd = th.mk_nth(s, i); + expr_ref s_i = th.mk_nth(s, i); expr_ref deriv(m); - deriv = derivative_wrapper(hd, r); + deriv = mk_derivative_wrapper(s_i, r); + STRACE("seq_regex", tout + << "mk_derivative_wrapper: " << re().to_str(deriv) << std::endl;); expr_ref accept_deriv(m); accept_deriv = mk_deriv_accept(s, idx + 1, deriv); accept_next.push_back(~lit); accept_next.push_back(len_s_le_i); accept_next.push_back(th.mk_literal(accept_deriv)); + // Acc(s, i, r) => (|s|<=i or Acc(s, i+1, D(s_i,r))) + // where Acc(s, i+1, ite(c, t, f)) = ite(c, Acc(s, i+1, t), Acc(s, i+1, t)) + // and Acc(s, i+1, r U s) = Acc(s, i+1, r) or Acc(s, i+1, s) th.add_axiom(accept_next); } @@ -418,20 +427,21 @@ namespace smt { /* Wrapper around calls to is_nullable from the seq rewriter. - Note: the nullable wrapper and derivative wrapper actually use + TODO: clean up the following: + Note: the is_nullable_wrapper and mk_derivative_wrapper actually use different sequence rewriters; these are at: m_seq_rewrite (returned by seq_rw()) th.m_rewrite.m_imp->m_cfg.m_seq_rw (private, can't be accessed directly) As a result operations are cached separately for the nullable - and derivative calls. TBD if caching them using the same rewriter - makes any difference. + and derivative calls. */ expr_ref seq_regex::is_nullable_wrapper(expr* r) { STRACE("seq_regex", tout << "nullable: " << mk_pp(r, m) << std::endl;); expr_ref result = seq_rw().is_nullable(r); + //TODO: rewrite seems unnecessary here rewrite(result); STRACE("seq_regex", tout << "nullable result: " << mk_pp(result, m) << std::endl;); @@ -442,39 +452,28 @@ namespace smt { } /* - Wrapper around the regex symbolic derivative from the seq rewriter. - Ensures that the derivative is written in a normalized BDD form - with optimizations for if-then-else expressions involving the head. - - Note: the nullable wrapper and derivative wrapper actually use - different sequence rewriters; these are at: - m_seq_rewrite - (returned by seq_rw()) - th.m_rewrite.m_imp->m_cfg.m_seq_rw - (private, can't be accessed directly) - As a result operations are cached separately for the nullable - and derivative calls. TBD if caching them using the same rewriter - makes any difference. + First creates a derivatrive of r wrt x=(:var 0) and then replaces x by ele. + This will create a cached entry for the generic derivative of r that is independent of ele. */ - expr_ref seq_regex::derivative_wrapper(expr* hd, expr* r) { - STRACE("seq_regex", tout << "derivative(" << mk_pp(hd, m) << "): " << mk_pp(r, m) << std::endl;); + expr_ref seq_regex::mk_derivative_wrapper(expr* ele, expr* r) { + STRACE("seq_regex", tout << "derivative(" << mk_pp(ele, m) << "): " << mk_pp(r, m) << std::endl;); - // Use canonical variable for head - expr_ref hd_canon(m.mk_var(0, hd->get_sort()), m); - expr_ref result(re().mk_derivative(hd_canon, r), m); - rewrite(result); + // Uses canonical variable (:var 0) for the derivative element + expr_ref der(seq_rw().mk_derivative(r), m); - // Substitute with real head + // Substitute (:var 0) with the actual element var_subst subst(m); expr_ref_vector sub(m); - sub.push_back(hd); - result = subst(result, sub); + sub.push_back(ele); + der = subst(der, sub); - STRACE("seq_regex", tout << "derivative result: " << mk_pp(result, m) << std::endl;); + STRACE("seq_regex", tout << "derivative result: " << mk_pp(der, m) << std::endl;); STRACE("seq_regex_brief", tout << "d(" << state_str(r) << ")=" - << state_str(result) << " ";); + << state_str(der) << " ";); - return result; + //TODO: simplify der further, if ele implies further simplifications + //e.g. if ele='b' then de(ite (x='a') t f) simplifies to t + return der; } void seq_regex::propagate_eq(expr* r1, expr* r2) { @@ -557,7 +556,7 @@ namespace smt { literal null_lit = th.mk_literal(is_nullable); expr_ref hd = mk_first(r, n); expr_ref d(m); - d = derivative_wrapper(hd, r); + d = mk_derivative_wrapper(hd, r); literal_vector lits; lits.push_back(~lit); @@ -584,11 +583,10 @@ namespace smt { } /* - Given a string s, index i, and a derivative regex d, return an + Given a string s, index i, and a derivative r, return an expression that is equivalent to accept s i r - but which pushes accept s i r into the leaves (next derivatives to - explore). + but which pushes accept s i r into the leaves Input r is of type regex; output is of type bool. @@ -600,14 +598,18 @@ namespace smt { expr_ref seq_regex::mk_deriv_accept(expr* s, unsigned i, expr* r) { vector to_visit; to_visit.push_back(r); - obj_map re_to_bool; + obj_map re_to_accept; expr_ref_vector _temp_bool_owner(m); // temp owner for bools we create - // DFS + bool s_is_longer_than_i = str().min_length(s) > i; + expr* i_int = a().mk_int(i); + _temp_bool_owner.push_back(i_int); + + // DFS, avoids duplicating derivative construction that has already been done while (to_visit.size() > 0) { expr* e = to_visit.back(); expr* econd = nullptr, *e1 = nullptr, *e2 = nullptr; - if (!re_to_bool.contains(e)) { + if (!re_to_accept.contains(e)) { // First visit: add children STRACE("seq_regex_verbose", tout << "1";); if (m.is_ite(e, econd, e1, e2) || @@ -616,36 +618,40 @@ namespace smt { to_visit.push_back(e2); } // Mark first visit by adding nullptr to the map - re_to_bool.insert(e, nullptr); + re_to_accept.insert(e, nullptr); } - else if (re_to_bool.find(e) == nullptr) { + else if (re_to_accept.find(e) == nullptr) { // Second visit: set value STRACE("seq_regex_verbose", tout << "2";); to_visit.pop_back(); if (m.is_ite(e, econd, e1, e2)) { - expr* b1 = re_to_bool.find(e1); - expr* b2 = re_to_bool.find(e2); - expr* b = m.mk_ite(econd, b1, b2); + expr* b1 = re_to_accept.find(e1); + expr* b2 = re_to_accept.find(e2); + expr* b = m.is_true(econd) || b1 == b2 ? b1 : m.is_false(econd) ? b2 : m.mk_ite(econd, b1, b2); _temp_bool_owner.push_back(b); - re_to_bool.find(e) = b; + re_to_accept.find(e) = b; + } + else if (re().is_empty(e) || (s_is_longer_than_i && re().is_epsilon(e))) + { + // s[i..] in [] <==> false, also: s[i..] in () <==> false when |s|>i + re_to_accept.find(e) = m.mk_false(); + } + else if (re().is_full_seq(e) || (s_is_longer_than_i && re().is_dot_plus(e))) + { + // s[i..] in .* <==> true, also: s[i..] in .+ <==> true when |s|>i + re_to_accept.find(e) = m.mk_true(); } /* - else if (re().is_empty(e)) - { - re_to_bool.find(e) = m.mk_false(); - } else if (re().is_epsilon(e)) { - expr* iplus1 = a().mk_int(i); expr* one = a().mk_int(1); - _temp_bool_owner.push_back(iplus1); _temp_bool_owner.push_back(one); - //the substring starting after position iplus1 must be empty - expr* s_end = str().mk_substr(s, iplus1, one); + //the substring starting after position i must be empty + expr* s_end = str().mk_substr(s, i_int, one); expr* s_end_is_epsilon = m.mk_eq(s_end, str().mk_empty(m.get_sort(s))); _temp_bool_owner.push_back(s_end_is_epsilon); - re_to_bool.find(e) = s_end_is_epsilon; + re_to_accept.find(e) = s_end_is_epsilon; STRACE("seq_regex_verbose", tout << "added empty sequence leaf: " @@ -653,18 +659,16 @@ namespace smt { } */ else if (re().is_union(e, e1, e2)) { - expr* b1 = re_to_bool.find(e1); - expr* b2 = re_to_bool.find(e2); - expr* b = m.mk_or(b1, b2); + expr* b1 = re_to_accept.find(e1); + expr* b2 = re_to_accept.find(e2); + expr* b = m.is_false(b1) || b1 == b2 ? b2 : m.is_false(b2) ? b1 : m.mk_or(b1, b2); _temp_bool_owner.push_back(b); - re_to_bool.find(e) = b; + re_to_accept.find(e) = b; } else { - expr* iplus1 = a().mk_int(i); - _temp_bool_owner.push_back(iplus1); - expr_ref acc_leaf = sk().mk_accept(s, iplus1, e); + expr_ref acc_leaf = sk().mk_accept(s, i_int, e); _temp_bool_owner.push_back(acc_leaf); - re_to_bool.find(e) = acc_leaf; + re_to_accept.find(e) = acc_leaf; STRACE("seq_regex_verbose", tout << "mk_deriv_accept: added accept leaf: " @@ -680,59 +684,44 @@ namespace smt { // Finalize expr_ref result(m); - result = re_to_bool.find(r); // Assigns ownership of all exprs in - // re_to_bool for after this completes + result = re_to_accept.find(r); // Assigns ownership of all exprs in + // re_to_accept for after this completes rewrite(result); return result; } /* - Return a list of all leaves in the derivative of a regex r, + Return a list of all target regexes in the derivative of a regex r, ignoring the conditions along each path. - Warning: Although the derivative - normal form tries to eliminate unsat condition paths, one cannot - assume that the path to each leaf is satisfiable in general - (e.g. when regexes are created using re.pred). - So not all results may correspond to satisfiable predicates. - It is OK to rely on the results being satisfiable for completeness, - but not soundness. + The derivative construction uses (:var 0) and tries + to eliminate unsat condition paths but it does not perform + full satisfiability checks and it is not guaranteed + that all targets are actually reachable */ - void seq_regex::get_all_derivatives(expr* r, expr_ref_vector& results) { - // Get derivative - sort* seq_sort = nullptr; - VERIFY(u().is_re(r, seq_sort)); - expr_ref n(m.mk_fresh_const("re.char", seq_sort), m); - expr_ref hd = mk_first(r, n); - expr_ref d(m); - d = derivative_wrapper(hd, r); + void seq_regex::get_derivative_targets(expr* r, expr_ref_vector& targets) { + // constructs the derivative wrt (:var 0) + expr_ref d(seq_rw().mk_derivative(r), m); - // DFS - vector to_visit; - to_visit.push_back(d); - obj_map visited; // set (bool is used as a unit type) - while (to_visit.size() > 0) { - expr* e = to_visit.back(); - to_visit.pop_back(); - if (visited.contains(e)) continue; - visited.insert(e, true); - expr* econd = nullptr, *e1 = nullptr, *e2 = nullptr; - if (m.is_ite(e, econd, e1, e2) || - re().is_union(e, e1, e2)) { - to_visit.push_back(e1); - to_visit.push_back(e2); - } - else if (!re().is_empty(e)) { - results.push_back(e); - STRACE("seq_regex_verbose", tout - << "get_all_derivatives: added deriv: " - << mk_pp(e, m) << std::endl;); + // use DFS to collect all the targets (leaf regexes) in d. + expr* _1 = nullptr, * e1 = nullptr, * e2 = nullptr; + obj_hashtable::entry* _2 = nullptr; + vector workset; + workset.push_back(d); + obj_hashtable done; + done.insert(d); + while (workset.size() > 0) { + expr* e = workset.back(); + workset.pop_back(); + if (m.is_ite(e, _1, e1, e2) || re().is_union(e, e1, e2)) { + if (done.insert_if_not_there_core(e1, _2)) + workset.push_back(e1); + if (done.insert_if_not_there_core(e2, _2)) + workset.push_back(e2); } + else if (!re().is_empty(e)) + targets.push_back(e); } - - STRACE("seq_regex", tout << "Number of derivatives: " - << results.size() << std::endl;); - STRACE("seq_regex_brief", tout << "#derivs=" << results.size() << " ";); } /* @@ -800,7 +789,7 @@ namespace smt { th.add_axiom(~lit, ~th.mk_literal(is_nullable)); expr_ref hd = mk_first(r, n); expr_ref d(m); - d = derivative_wrapper(hd, r); + d = mk_derivative_wrapper(hd, r); literal_vector lits; expr_ref_pair_vector cofactors(m); get_cofactors(d, cofactors); @@ -884,7 +873,7 @@ namespace smt { STRACE("state_graph", if (!m_state_graph.is_seen(r_id)) - tout << std::endl << "state(" << r_id << ") = " << seq_util::rex::pp(re(), r) << std::endl << "info(" << r_id << ") = " << re().get_info(r) << std::endl;); + tout << std::endl << "state(" << r_id << ") = " << re().to_str(r) << std::endl << "info(" << r_id << ") = " << re().get_info(r) << std::endl;); // Add state m_state_graph.add_state(r_id); STRACE("seq_regex", tout << "Updating state graph for regex " @@ -900,14 +889,14 @@ namespace smt { expr_ref_vector derivatives(m); STRACE("seq_regex_verbose", tout << "getting all derivs: " << r_id << " " << std::endl;); - get_all_derivatives(r, derivatives); + get_derivative_targets(r, derivatives); for (auto const& dr: derivatives) { unsigned dr_id = get_state_id(dr); STRACE("seq_regex_verbose", tout << std::endl << " traversing deriv: " << dr_id << " ";); STRACE("state_graph", if (!m_state_graph.is_seen(dr_id)) - tout << "state(" << dr_id << ") = " << seq_util::rex::pp(re(), dr) << std::endl << "info(" << dr_id << ") = " << re().get_info(dr) << std::endl;); + tout << "state(" << dr_id << ") = " << re().to_str(dr) << std::endl << "info(" << dr_id << ") = " << re().get_info(dr) << std::endl;); // Add state m_state_graph.add_state(dr_id); bool maybecycle = can_be_in_cycle(r, dr); diff --git a/src/smt/seq_regex.h b/src/smt/seq_regex.h index 8a2cf68f1..dcf81dbc3 100644 --- a/src/smt/seq_regex.h +++ b/src/smt/seq_regex.h @@ -158,12 +158,12 @@ namespace smt { expr_ref symmetric_diff(expr* r1, expr* r2); expr_ref is_nullable_wrapper(expr* r); - expr_ref derivative_wrapper(expr* hd, expr* r); + expr_ref mk_derivative_wrapper(expr* hd, expr* r); // Various support for unfolding derivative expressions that are // returned by derivative_wrapper expr_ref mk_deriv_accept(expr* s, unsigned i, expr* r); - void get_all_derivatives(expr* r, expr_ref_vector& results); + void get_derivative_targets(expr* r, expr_ref_vector& targets); void get_cofactors(expr* r, expr_ref_pair_vector& result); void get_cofactors_rec(expr* r, expr_ref_vector& conds, expr_ref_pair_vector& result); diff --git a/src/smt/smt_conflict_resolution.cpp b/src/smt/smt_conflict_resolution.cpp index 14a71ae28..45ff1900d 100644 --- a/src/smt/smt_conflict_resolution.cpp +++ b/src/smt/smt_conflict_resolution.cpp @@ -413,6 +413,7 @@ namespace smt { // of justification are considered level zero. if (m_conflict_lvl <= m_ctx.get_search_level()) { TRACE("conflict", tout << "problem is unsat\n";); + TRACE("conflict", m_ctx.display(tout);); if (m.proofs_enabled()) mk_conflict_proof(conflict, not_l); if (m_ctx.tracking_assumptions()) @@ -473,7 +474,8 @@ namespace smt { TRACE("conflict", tout << "new scope level: " << m_new_scope_lvl << "\n"; - tout << "intern. scope level: " << m_lemma_iscope_lvl << "\n";); + tout << "intern. scope level: " << m_lemma_iscope_lvl << "\n"; + tout << "lemma: " << m_lemma << "\n";); if (m.proofs_enabled()) mk_conflict_proof(conflict, not_l); @@ -760,6 +762,7 @@ namespace smt { m_lemma .shrink(j); m_lemma_atoms.shrink(j); m_ctx.m_stats.m_num_minimized_lits += sz - j; + TRACE("conflict", tout << "lemma: " << m_lemma << "\n";); } /** @@ -1359,9 +1362,8 @@ namespace smt { void conflict_resolution::process_antecedent_for_unsat_core(literal antecedent) { bool_var var = antecedent.var(); - TRACE("conflict", tout << "processing antecedent: "; + CTRACE("conflict", !m_ctx.is_marked(var), tout << "processing antecedent: "; m_ctx.display_literal_info(tout << antecedent << " ", antecedent); - tout << (m_ctx.is_marked(var)?"marked":"not marked"); tout << "\n";); if (!m_ctx.is_marked(var)) { diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 71aeebd6a..ee3341e43 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -34,6 +34,7 @@ Revision History: #include "smt/smt_context.h" #include "smt/smt_quick_checker.h" #include "smt/uses_theory.h" +#include "smt/theory_special_relations.h" #include "smt/smt_for_each_relevant_expr.h" #include "smt/smt_model_generator.h" #include "smt/smt_model_checker.h" @@ -1543,6 +1544,14 @@ namespace smt { } } + void context::get_specrels(func_decl_set& rels) const { + family_id fid = m.get_family_id("specrels"); + theory* th = get_theory(fid); + if (th) + dynamic_cast(th)->get_specrels(rels); + } + + void context::relevant_eh(expr * n) { if (b_internalized(n)) { bool_var v = get_bool_var(n); diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index a607deab3..b5a19569a 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -279,6 +279,9 @@ namespace smt { return m_app2enode[n->get_id()]; } + void get_specrels(func_decl_set& rels) const; + + /** \brief Similar to get_enode, but returns 0 if n is to e_internalized. */ @@ -1189,7 +1192,6 @@ namespace smt { bool more_than_k_unassigned_literals(clause * cls, unsigned k); - void internalize_assertions(); void asserted_inconsistent(); @@ -1606,6 +1608,8 @@ namespace smt { void assert_expr(expr * e, proof * pr); + void internalize_assertions(); + void push(); void pop(unsigned num_scopes); diff --git a/src/smt/smt_context_pp.cpp b/src/smt/smt_context_pp.cpp index 3f826c2d1..4499752e5 100644 --- a/src/smt/smt_context_pp.cpp +++ b/src/smt/smt_context_pp.cpp @@ -113,9 +113,17 @@ namespace smt { } std::ostream& context::display_literals_smt2(std::ostream& out, unsigned num_lits, literal const* lits) const { - for (unsigned i = 0; i < num_lits; ++i) { + out << literal_vector(num_lits, lits) << ":\n"; +#if 1 + expr_ref_vector fmls(m); + for (unsigned i = 0; i < num_lits; ++i) + fmls.push_back(literal2expr(lits[i])); + expr_ref c = mk_or(fmls); + out << c << "\n"; +#else + for (unsigned i = 0; i < num_lits; ++i) display_literal_smt2(out, lits[i]) << "\n"; - } +#endif return out; } @@ -254,22 +262,30 @@ namespace smt { } void context::display_eqc(std::ostream & out) const { - bool first = true; - for (enode * x : m_enodes) { - expr * n = x->get_expr(); - expr * r = x->get_root()->get_expr(); - if (n != r) { - if (first) { - out << "equivalence classes:\n"; - first = false; - } - out << "#" << n->get_id() << " -> #" << r->get_id() << ": "; - out << mk_pp(n, m) << " -> " << mk_pp(r, m) << "\n"; + if (m_enodes.empty()) + return; + unsigned count = 0; + for (enode * r : m_enodes) + if (r->is_root()) + ++count; + + out << "equivalence classes: " << count << "\n"; + for (enode * r : m_enodes) { + if (!r->is_root()) + continue; + out << "#" << enode_pp(r, *this) << "\n"; + if (r->get_class_size() == 1) + continue; + for (enode* n : *r) { + if (n != r) + out << " #" << enode_pp(n, *this) << "\n"; } } } void context::display_app_enode_map(std::ostream & out) const { + return; + // mainly useless if (!m_e_internalized_stack.empty()) { out << "expression -> enode:\n"; unsigned sz = m_e_internalized_stack.size(); @@ -668,7 +684,7 @@ namespace smt { std::ostream& operator<<(std::ostream& out, enode_pp const& p) { ast_manager& m = p.ctx.get_manager(); enode* n = p.n; - return out << "[#" << n->get_owner_id() << " " << mk_bounded_pp(n->get_expr(), m) << "]"; + return out << n->get_owner_id() << ": " << mk_bounded_pp(n->get_expr(), m); } std::ostream& operator<<(std::ostream& out, enode_eq_pp const& p) { diff --git a/src/smt/smt_enode.cpp b/src/smt/smt_enode.cpp index a54a287ec..49f05b019 100644 --- a/src/smt/smt_enode.cpp +++ b/src/smt/smt_enode.cpp @@ -50,7 +50,7 @@ namespace smt { n->m_lbl_hash = -1; n->m_proof_is_logged = false; unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { + for (unsigned i = 0; i < num_args; i++) { enode * arg = app2enode[owner->get_arg(i)->get_id()]; n->m_args[i] = arg; SASSERT(n->get_arg(i) == arg); diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 4a4aed0f4..77b3f14d5 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -1387,7 +1387,7 @@ namespace smt { default: break; } - TRACE("mk_clause", display_literals_verbose(tout << "after simplification:\n", num_lits, lits) << "\n";); + TRACE("mk_clause", display_literals_verbose(tout << "after simplification: " << literal_vector(num_lits, lits) << "\n", num_lits, lits) << "\n";); unsigned activity = 1; bool lemma = is_lemma(k); diff --git a/src/smt/smt_model_finder.cpp b/src/smt/smt_model_finder.cpp index ed663fbea..34f323695 100644 --- a/src/smt/smt_model_finder.cpp +++ b/src/smt/smt_model_finder.cpp @@ -50,6 +50,7 @@ namespace smt { class evaluator { public: + virtual ~evaluator() = default; virtual expr* eval(expr* n, bool model_completion) = 0; }; @@ -401,6 +402,7 @@ namespace smt { expr_ref_vector* m_new_constraints{ nullptr }; random_gen m_rand; + func_decl_set m_specrels; void reset_sort2k() { @@ -470,6 +472,7 @@ namespace smt { ast_manager& get_manager() const { return m; } void reset() { + m_specrels.reset(); flush_nodes(); m_nodes.reset(); m_next_node_id = 0; @@ -479,6 +482,11 @@ namespace smt { reset_sort2k(); } + void set_specrels(context& c) { + m_specrels.reset(); + c.get_specrels(m_specrels); + } + void set_model(proto_model* m) { reset_eval_cache(); m_model = m; @@ -1049,9 +1057,13 @@ namespace smt { */ void complete_partial_funcs(func_decl_set const& partial_funcs) { for (func_decl* f : partial_funcs) { + // Complete the current interpretation m_model->complete_partial_func(f, true); + if (m_specrels.contains(f)) + continue; + unsigned arity = f->get_arity(); func_interp* fi = m_model->get_func_interp(f); if (fi->is_constant()) @@ -2399,9 +2411,11 @@ namespace smt { } } + void model_finder::process_auf(ptr_vector const& qs, proto_model* mdl) { m_auf_solver->reset(); m_auf_solver->set_model(mdl); + m_auf_solver->set_specrels(*m_context); for (quantifier* q : qs) { quantifier_info* qi = get_quantifier_info(q); diff --git a/src/smt/smt_parallel.cpp b/src/smt/smt_parallel.cpp index 744d5ce2b..82bda1d39 100644 --- a/src/smt/smt_parallel.cpp +++ b/src/smt/smt_parallel.cpp @@ -149,21 +149,18 @@ namespace smt { expr_ref c(pm); pctx.get_fparams().m_max_conflicts = std::min(thread_max_conflicts, max_conflicts); - if (num_rounds > 0 && (pctx.get_fparams().m_threads_cube_frequency % num_rounds) == 0) { + if (num_rounds > 0 && (pctx.get_fparams().m_threads_cube_frequency % num_rounds) == 0) cube(pctx, lasms, c); - } IF_VERBOSE(1, verbose_stream() << "(smt.thread " << i; if (num_rounds > 0) verbose_stream() << " :round " << num_rounds; if (c) verbose_stream() << " :cube " << mk_bounded_pp(c, pm, 3); verbose_stream() << ")\n";); lbool r = pctx.check(lasms.size(), lasms.data()); - if (r == l_undef && pctx.m_num_conflicts >= max_conflicts) { - // no-op - } - else if (r == l_undef && pctx.m_num_conflicts >= thread_max_conflicts) { + if (r == l_undef && pctx.m_num_conflicts >= max_conflicts) + ; // no-op + else if (r == l_undef && pctx.m_num_conflicts >= thread_max_conflicts) return; - } else if (r == l_false && pctx.unsat_core().contains(c)) { IF_VERBOSE(1, verbose_stream() << "(smt.thread " << i << " :learn " << mk_bounded_pp(c, pm, 3) << ")"); pctx.assert_expr(mk_not(mk_and(pctx.unsat_core()))); diff --git a/src/smt/smt_relevancy.h b/src/smt/smt_relevancy.h index 53bf4807d..87cc3847c 100644 --- a/src/smt/smt_relevancy.h +++ b/src/smt/smt_relevancy.h @@ -169,12 +169,12 @@ namespace smt { bool enabled() const; /** - \Brief Return the region allocator for the smt::context that owns this propagator. + \brief Return the region allocator for the smt::context that owns this propagator. */ region & get_region() const; /** - \Brief Return the ast_manager for the smt::context that owns this propagator. + \brief Return the ast_manager for the smt::context that owns this propagator. */ ast_manager & get_manager() const; diff --git a/src/smt/smt_theory.cpp b/src/smt/smt_theory.cpp index 60f8dc3ae..0c876e8bc 100644 --- a/src/smt/smt_theory.cpp +++ b/src/smt/smt_theory.cpp @@ -208,10 +208,11 @@ namespace smt { } void theory::log_axiom_instantiation(app * r, unsigned axiom_id, unsigned num_bindings, app * const * bindings, unsigned pattern_id, const vector> & used_enodes) { - ast_manager & m = get_manager(); - app_ref _r(r, m); + ast_manager & m = get_manager(); + SASSERT(r->get_ref_count() > 0); std::ostream& out = m.trace_stream(); symbol const & family_name = m.get_family_name(get_family_id()); + if (pattern_id == UINT_MAX) { out << "[inst-discovered] theory-solving " << static_cast(nullptr) << " " << family_name << "#"; if (axiom_id != UINT_MAX) diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 7799acf24..500c47f5e 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -453,9 +453,6 @@ namespace smt { std::ostream& display_flat_app(std::ostream & out, app * n) const; - std::ostream& display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_expr()); } - - std::ostream& display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_expr()); } protected: void log_axiom_instantiation(app * r, unsigned axiom_id = UINT_MAX, unsigned num_bindings = 0, @@ -481,6 +478,7 @@ namespace smt { } void log_axiom_unit(app* r) { + expr_ref _r(r, m); log_axiom_instantiation(r); m.trace_stream() << "[end-of-instance]\n"; } diff --git a/src/smt/theory_arith_aux.h b/src/smt/theory_arith_aux.h index f152c2430..4b57f043e 100644 --- a/src/smt/theory_arith_aux.h +++ b/src/smt/theory_arith_aux.h @@ -2239,17 +2239,17 @@ namespace smt { ctx.push_trail(value_trail(m_assume_eq_head)); while (m_assume_eq_head < m_assume_eq_candidates.size()) { - std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; - theory_var v1 = p.first; - theory_var v2 = p.second; + auto const& [v1, v2] = m_assume_eq_candidates[m_assume_eq_head]; + enode* n1 = get_enode(v1); + enode* n2 = get_enode(v2); m_assume_eq_head++; CTRACE("func_interp_bug", get_value(v1) == get_value(v2) && - get_enode(v1)->get_root() != get_enode(v2)->get_root(), - tout << "assuming eq: #" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); + n1->get_root() != n2->get_root(), + tout << "assuming eq: #" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n";); if (get_value(v1) == get_value(v2) && - get_enode(v1)->get_root() != get_enode(v2)->get_root() && - assume_eq(get_enode(v1), get_enode(v2))) { + n1->get_root() != n2->get_root() && + assume_eq(n1, n2)) { ++m_stats.m_assume_eqs; return true; } diff --git a/src/smt/theory_arith_pp.h b/src/smt/theory_arith_pp.h index a4dba46d0..049528f79 100644 --- a/src/smt/theory_arith_pp.h +++ b/src/smt/theory_arith_pp.h @@ -82,18 +82,17 @@ namespace smt { template void theory_arith::display_row(std::ostream & out, row const & r, bool compact) const { - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); + out << "(v" << r.get_base_var() << ") : "; bool first = true; - for (; it != end; ++it) { - if (!it->is_dead()) { + for (auto const& e : r) { + if (!e.is_dead()) { if (first) first = false; else out << " + "; - theory_var s = it->m_var; - numeral const & c = it->m_coeff; + theory_var s = e.m_var; + numeral const & c = e.m_coeff; if (!c.is_one()) out << c << "*"; if (compact) { @@ -103,7 +102,7 @@ namespace smt { } } else - display_var_flat_def(out, s); + out << enode_pp(get_enode(s), ctx); } } out << "\n"; @@ -117,20 +116,16 @@ namespace smt { else out << "rows (expanded view):\n"; unsigned num = m_rows.size(); - for (unsigned r_id = 0; r_id < num; r_id++) { - if (m_rows[r_id].m_base_var != null_theory_var) { + for (unsigned r_id = 0; r_id < num; r_id++) + if (m_rows[r_id].m_base_var != null_theory_var) display_row(out, r_id, compact); - } - } } template void theory_arith::display_row_shape(std::ostream & out, row const & r) const { - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - numeral const & c = it->m_coeff; + for (auto const& e : r) { + if (!e.is_dead()) { + numeral const & c = e.m_coeff; if (c.is_one()) out << "1"; else if (c.is_minus_one()) @@ -150,11 +145,9 @@ namespace smt { template bool theory_arith::is_one_minus_one_row(row const & r) const { - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - numeral const & c = it->m_coeff; + for (auto const& e : r) { + if (!e.is_dead()) { + numeral const & c = e.m_coeff; if (!c.is_one() && !c.is_minus_one()) return false; } @@ -184,11 +177,9 @@ namespace smt { for (unsigned r_id = 0; r_id < num; r_id++) { row const & r = m_rows[r_id]; if (r.m_base_var != null_theory_var) { - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - numeral const & c = it->m_coeff; + for (auto const& e : r) { + if (!e.is_dead()) { + numeral const & c = e.m_coeff; if (c.to_rational().is_big()) { std::string str = c.to_rational().to_string(); if (str.length() > 48) @@ -215,11 +206,9 @@ namespace smt { row const & r = m_rows[r_id]; if (r.m_base_var != null_theory_var) { num_rows++; - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - numeral const & c = it->m_coeff; + for (auto const& e : r) { + if (!e.is_dead()) { + numeral const & c = e.m_coeff; num_non_zeros++; if (c.is_one()) num_ones++; @@ -284,11 +273,9 @@ namespace smt { template void theory_arith::display_row_info(std::ostream & out, row const & r) const { display_row(out, r, true); - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) - if (!it->is_dead()) - display_var(out, it->m_var); + for (auto const& e : r) + if (!e.is_dead()) + display_var(out, e.m_var); } /** @@ -298,15 +285,14 @@ namespace smt { void theory_arith::display_simplified_row(std::ostream & out, row const & r) const { bool has_rat_coeff = false; numeral k; - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); + out << "(v" << r.get_base_var() << ") : "; bool first = true; - for (; it != end; ++it) { - if (it->is_dead()) + for (auto const& e : r) { + if (e.is_dead()) continue; - theory_var v = it->m_var; - numeral const & c = it->m_coeff; + theory_var v = e.m_var; + numeral const & c = e.m_coeff; if (is_fixed(v)) { k += c * lower_bound(v).get_rational(); continue; @@ -328,11 +314,9 @@ namespace smt { } out << "\n"; if (has_rat_coeff) { - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) - if (!it->is_dead() && (is_base(it->m_var) || (!is_fixed(it->m_var) && (lower(it->m_var) || upper(it->m_var))))) - display_var(out, it->m_var); + for (auto const& e : r) + if (!e.is_dead() && (is_base(e.m_var) || (!is_fixed(e.m_var) && (lower(e.m_var) || upper(e.m_var))))) + display_var(out, e.m_var); } } @@ -385,8 +369,7 @@ namespace smt { out << ", shared: " << get_context().is_shared(get_enode(v)); out << ", unassigned: " << m_unassigned_atoms[v]; out << ", rel: " << get_context().is_relevant(get_enode(v)); - out << ", def: "; - display_var_flat_def(out, v); + out << ", def: " << enode_pp(get_enode(v), ctx); out << "\n"; } @@ -477,28 +460,17 @@ namespace smt { theory_var v = a->get_var(); inf_numeral const & k = a->get_k(); enode * e = get_enode(v); - if (show_sign) { - if (!a->is_true()) - out << "not "; - else - out << " "; - } + if (show_sign) + out << (a->is_true()?" ":"not "); out << "v"; out.width(3); out << std::left << v << " #"; out.width(3); out << e->get_owner_id(); out << std::right; - out << " "; - if (a->get_atom_kind() == A_LOWER) - out << ">="; - else - out << "<="; - out << " "; + out << " " << ((a->get_atom_kind() == A_LOWER)? ">=" : "<=") << " "; out.width(6); - out << k << " "; - display_var_flat_def(out, v); - out << "\n"; + out << k << " " << enode_pp(get_enode(v), ctx) << "\n"; } template diff --git a/src/smt/theory_array_full.cpp b/src/smt/theory_array_full.cpp index 15214a81b..6e7403737 100644 --- a/src/smt/theory_array_full.cpp +++ b/src/smt/theory_array_full.cpp @@ -707,6 +707,38 @@ namespace smt { } else if (!has_large_domain(store_app)) { // + // let A = store(B, i, v) + // + // Add: + // default(A) = A[epsilon] + // default(B) = B[epsilon] + // + // + expr_ref_vector args1(m), args2(m); + args1.push_back(store_app->get_arg(0)); + args2.push_back(store_app); + + for (unsigned i = 1; i + 1 < num_args; ++i) { + expr* arg = store_app->get_arg(i); + sort* srt = arg->get_sort(); + auto ep = mk_epsilon(srt); + args1.push_back(ep.first); + args2.push_back(ep.first); + } + app_ref sel1(m), sel2(m); + sel1 = mk_select(args1); + sel2 = mk_select(args2); + ctx.internalize(def1, false); + ctx.internalize(def2, false); + is_new = try_assign_eq(def1, sel1) || try_assign_eq(def2, sel2); + return is_new; + + +#if 0 + // + // This is incorrect, issue #5593. + // + // let A = store(B, i, v) // // Add: @@ -731,7 +763,9 @@ namespace smt { app_ref sel1(m), sel2(m); sel1 = mk_select(args1); sel2 = mk_select(args2); + std::cout << "small domain " << sel1 << " " << sel2 << "\n"; is_new = try_assign_eq(sel1, sel2); +#endif } ctx.internalize(def1, false); diff --git a/src/smt/theory_datatype.cpp b/src/smt/theory_datatype.cpp index 80b3b5062..765513823 100644 --- a/src/smt/theory_datatype.cpp +++ b/src/smt/theory_datatype.cpp @@ -297,7 +297,7 @@ namespace smt { TRACE("datatype", tout << "internalizing term:\n" << mk_pp(term, m) << "\n";); unsigned num_args = term->get_num_args(); for (unsigned i = 0; i < num_args; i++) - ctx.internalize(term->get_arg(i), false); + ctx.internalize(term->get_arg(i), has_quantifiers(term)); // the internalization of the arguments may trigger the internalization of term. if (ctx.e_internalized(term)) return true; diff --git a/src/smt/theory_fpa.cpp b/src/smt/theory_fpa.cpp index 1543ac2c2..ee547b22d 100644 --- a/src/smt/theory_fpa.cpp +++ b/src/smt/theory_fpa.cpp @@ -38,7 +38,7 @@ namespace smt { { params_ref p; p.set_bool("arith_lhs", true); - m_th_rw.updt_params(p); + m_th_rw.updt_params(p); } theory_fpa::~theory_fpa() @@ -215,11 +215,12 @@ namespace smt { } void theory_fpa::assert_cnstr(expr * e) { + expr_ref _e(e, m); if (m.is_true(e)) return; TRACE("t_fpa_detail", tout << "asserting " << mk_ismt2_pp(e, m) << "\n";); if (m.has_trace_stream()) log_axiom_instantiation(e); ctx.internalize(e, false); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; + if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); @@ -284,6 +285,9 @@ namespace smt { } default: /* ignore */; } + + if (!ctx.relevancy()) + relevant_eh(term); } return true; @@ -454,13 +458,9 @@ namespace smt { expr * args[] = { bv_val_a->get_arg(0), bv_val_a->get_arg(1), bv_val_a->get_arg(2) }; cc_args = m_bv_util.mk_concat(3, args); c = m.mk_eq(wrapped, cc_args); - // NB code review: #5454 exposes a bug in fpa_solver that - // could be latent here as well. It needs also the equality - // n == bv_val_e to be asserted such that whenever something is assigned th - // bit-vector value cc_args it is equated with n - // I don't see another way this constraint would be enforced. assert_cnstr(c); assert_cnstr(mk_side_conditions()); + assert_cnstr(m.mk_eq(n, bv_val_e)); } else { expr_ref wu(m); @@ -627,7 +627,7 @@ namespace smt { bv2fp.convert_min_max_specials(&mdl, &new_model, seen); bv2fp.convert_uf2bvuf(&mdl, &new_model, seen); - for (func_decl* f : seen) + for (func_decl* f : seen) mdl.unregister_decl(f); for (unsigned i = 0; i < new_model.get_num_constants(); i++) { diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 124461189..0abc2870d 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -173,8 +173,8 @@ class theory_lra::imp { unsigned_vector m_bounds_trail; unsigned m_asserted_qhead; - svector m_to_check; // rows that should be checked for theory propagation - + svector m_bv_to_propagate; // Boolean variables that can be propagated + svector > m_assume_eq_candidates; unsigned m_assume_eq_head; lp::u_set m_tmp_var_set; @@ -233,6 +233,7 @@ class theory_lra::imp { resource_limit m_resource_limit; lp_bounds m_new_bounds; symbol m_farkas; + vector m_bound_params; lp::lp_bound_propagator m_bp; context& ctx() const { return th.get_context(); } @@ -870,6 +871,10 @@ public: m_bound_terms(m), m_bound_predicate(m) { + m_bound_params.push_back(parameter(m_farkas)); + m_bound_params.push_back(parameter(rational(1))); + m_bound_params.push_back(parameter(rational(1))); + } ~imp() { @@ -1071,7 +1076,7 @@ public: lp().pop(num_scopes); // VERIFY(l_false != make_feasible()); m_new_bounds.reset(); - m_to_check.reset(); + m_bv_to_propagate.reset(); if (m_nla) m_nla->pop(num_scopes); TRACE("arith", tout << "num scopes: " << num_scopes << " new scope level: " << m_scopes.size() << "\n";); @@ -1493,29 +1498,24 @@ public: ctx().push_trail(value_trail(m_assume_eq_head)); while (m_assume_eq_head < m_assume_eq_candidates.size()) { - std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; - theory_var v1 = p.first; - theory_var v2 = p.second; + auto const [v1, v2] = m_assume_eq_candidates[m_assume_eq_head]; enode* n1 = get_enode(v1); enode* n2 = get_enode(v2); m_assume_eq_head++; CTRACE("arith", is_eq(v1, v2) && n1->get_root() != n2->get_root(), tout << "assuming eq: v" << v1 << " = v" << v2 << "\n";); - if (is_eq(v1, v2) && n1->get_root() != n2->get_root() && th.assume_eq(n1, n2)) { + if (is_eq(v1, v2) && n1->get_root() != n2->get_root() && th.assume_eq(n1, n2)) return true; - } } return false; } bool is_eq(theory_var v1, theory_var v2) { - if (use_nra_model()) { + if (use_nra_model()) return m_nla->am().eq(nl_value(v1, *m_a1), nl_value(v2, *m_a2)); - } - else { + else return get_ivalue(v1) == get_ivalue(v2); - } } bool has_delayed_constraints() const { @@ -1523,6 +1523,8 @@ public: } final_check_status final_check_eh() { + if (propagate_core()) + return FC_CONTINUE; m_model_is_initialized = false; IF_VERBOSE(12, verbose_stream() << "final-check " << lp().get_status() << "\n"); lbool is_sat = l_true; @@ -1534,9 +1536,7 @@ public: switch (is_sat) { case l_true: - TRACE("arith", display(tout); - /* ctx().display(tout);*/ - ); + TRACE("arith", display(tout)); switch (check_lia()) { case l_true: @@ -2048,41 +2048,59 @@ public: return false; } - bool m_new_def{ false }; + bool m_new_def = false ; + + bool adaptive() const { return ctx().get_fparams().m_arith_adaptive; } + double adaptive_assertion_threshold() const { return ctx().get_fparams().m_arith_adaptive_assertion_threshold; } + + bool process_atoms() const { + if (!adaptive()) + return true; + unsigned total_conflicts = ctx().get_num_conflicts(); + if (total_conflicts < 10) + return true; + double f = static_cast(m_num_conflicts)/static_cast(total_conflicts); + return f >= adaptive_assertion_threshold(); + } bool can_propagate() { + return process_atoms() && can_propagate_core(); + } + + bool can_propagate_core() { return m_asserted_atoms.size() > m_asserted_qhead || m_new_def; } - void propagate() { + bool propagate() { + return process_atoms() && propagate_core(); + } + + bool propagate_core() { m_model_is_initialized = false; flush_bound_axioms(); - if (!can_propagate()) { - return; - } - m_new_def = false; + if (!can_propagate_core()) + return false; + m_new_def = false; while (m_asserted_qhead < m_asserted_atoms.size() && !ctx().inconsistent() && m.inc()) { - bool_var bv = m_asserted_atoms[m_asserted_qhead].m_bv; - bool is_true = m_asserted_atoms[m_asserted_qhead].m_is_true; - m_to_check.push_back(bv); + auto [bv, is_true] = m_asserted_atoms[m_asserted_qhead]; + + m_bv_to_propagate.push_back(bv); + api_bound* b = nullptr; - TRACE("arith", tout << "propagate: " << literal(bv, !is_true) << "\n";); - if (m_bool_var2bound.find(bv, b)) { + TRACE("arith", tout << "propagate: " << literal(bv, !is_true) << "\n"; + if (!m_bool_var2bound.contains(bv)) tout << "not found\n"); + if (m_bool_var2bound.find(bv, b)) assert_bound(bv, is_true, *b); - } - else { - TRACE("arith", tout << "not found " << bv << "\n";); - } ++m_asserted_qhead; } if (ctx().inconsistent()) { - m_to_check.reset(); - return; + m_bv_to_propagate.reset(); + return true; } lbool lbl = make_feasible(); if (!m.inc()) - return; + return false; switch(lbl) { case l_false: @@ -2096,7 +2114,7 @@ public: case l_undef: break; } - + return true; } bool should_propagate() const { @@ -2246,26 +2264,28 @@ public: assign(bound, m_core, m_eqs, m_params); } - void add_eq(lpvar u, lpvar v, lp::explanation const& e) { + bool add_eq(lpvar u, lpvar v, lp::explanation const& e, bool is_fixed) { if (ctx().inconsistent()) - return; + return false; theory_var uv = lp().local_to_external(u); // variables that are returned should have external representations theory_var vv = lp().local_to_external(v); // so maybe better to have them already transformed to external form enode* n1 = get_enode(uv); enode* n2 = get_enode(vv); + TRACE("arith", tout << "add-eq " << mk_pp(n1->get_expr(), m) << " == " << mk_pp(n2->get_expr(), m) << " " << n1->get_expr_id() << " == " << n2->get_expr_id() << "\n";); if (n1->get_root() == n2->get_root()) - return; + return false; expr* e1 = n1->get_expr(); expr* e2 = n2->get_expr(); if (e1->get_sort() != e2->get_sort()) - return; - if (m.is_ite(e1) || m.is_ite(e2)) - return; + return false; + if (!is_fixed && !a.is_numeral(e1) && !a.is_numeral(e2) && (m.is_ite(e1) || m.is_ite(e2))) + return false; reset_evidence(); for (auto ev : e) set_evidence(ev.ci(), m_core, m_eqs); assign_eq(uv, vv); + return true; } literal_vector m_core2; @@ -2440,6 +2460,7 @@ public: typedef lp_bounds::iterator iterator; void flush_bound_axioms() { + CTRACE("arith", !m_new_bounds.empty(), tout << "flush bound axioms\n";); while (!m_new_bounds.empty()) { @@ -2458,7 +2479,7 @@ public: CTRACE("arith", atoms.size() > 1, for (auto* a : atoms) a->display(tout) << "\n";); lp_bounds occs(m_bounds[v]); - + std::sort(atoms.begin(), atoms.end(), compare_bounds()); std::sort(occs.begin(), occs.end(), compare_bounds()); @@ -2558,14 +2579,15 @@ public: } void propagate_basic_bounds() { - for (auto const& bv : m_to_check) { + for (auto const& bv : m_bv_to_propagate) { api_bound* b = nullptr; if (m_bool_var2bound.find(bv, b)) { propagate_bound(bv, ctx().get_assignment(bv) == l_true, *b); - if (ctx().inconsistent()) break; + if (ctx().inconsistent()) + break; } } - m_to_check.reset(); + m_bv_to_propagate.reset(); } // for glb lo': lo' < lo: @@ -2633,10 +2655,7 @@ public: ctx().display_literals_verbose(tout, m_core); ctx().display_literal_verbose(tout << " => ", lit2); tout << "\n";); - m_params.push_back(parameter(m_farkas)); - m_params.push_back(parameter(rational(1))); - m_params.push_back(parameter(rational(1))); - assign(lit2, m_core, m_eqs, m_params); + assign(lit2, m_core, m_eqs, m_bound_params); ++m_stats.m_bounds_propagations; } @@ -3194,7 +3213,7 @@ public: m_assume_eq_head = 0; m_scopes.reset(); m_stats.reset(); - m_to_check.reset(); + m_bv_to_propagate.reset(); m_model_is_initialized = false; } @@ -3661,7 +3680,7 @@ public: else if (can_get_value(v)) out << " = " << get_value(v); if (is_int(v)) out << ", int"; if (ctx().is_shared(get_enode(v))) out << ", shared"; - out << " := "; th.display_var_flat_def(out, v) << "\n"; + out << " := " << enode_pp(get_enode(v), ctx()) << "\n"; } } diff --git a/src/smt/theory_opt.h b/src/smt/theory_opt.h index a9eb2321b..9e2fa9295 100644 --- a/src/smt/theory_opt.h +++ b/src/smt/theory_opt.h @@ -29,6 +29,7 @@ namespace smt { class theory_opt { public: typedef inf_eps_rational inf_eps; + virtual ~theory_opt() = default; virtual inf_eps value(theory_var) = 0; virtual inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared) = 0; virtual theory_var add_objective(app* term) = 0; diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index e21eb2bc5..1bf987008 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -246,17 +246,23 @@ namespace smt { literal theory_recfun::mk_eq_lit(expr* l, expr* r) { literal lit; - if (m.is_true(r) || m.is_false(r)) { - std::swap(l, r); - } - if (m.is_true(l)) { - lit = mk_literal(r); - } - else if (m.is_false(l)) { - lit = ~mk_literal(r); + if (has_quantifiers(l) || has_quantifiers(r)) { + expr_ref eq1(m.mk_eq(l, r), m); + expr_ref fn(m.mk_fresh_const("rec-eq", m.mk_bool_sort()), m); + expr_ref eq(m.mk_eq(fn, eq1), m); + ctx.assert_expr(eq); + ctx.internalize_assertions(); + lit = mk_literal(fn); } else { - lit = mk_eq(l, r, false); + if (m.is_true(r) || m.is_false(r)) + std::swap(l, r); + if (m.is_true(l)) + lit = mk_literal(r); + else if (m.is_false(l)) + lit = ~mk_literal(r); + else + lit = mk_eq(l, r, false); } ctx.mark_as_relevant(lit); return lit; @@ -282,14 +288,12 @@ namespace smt { auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m); unsigned depth = get_depth(e.m_lhs); - expr_ref rhs(apply_args(depth, vars, e.m_args, e.m_def->get_rhs()), m); + expr_ref rhs(apply_args(depth, vars, e.m_args, e.m_def->get_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); std::function fn = [&]() { return lit; }; scoped_trace_stream _tr(*this, fn); ctx.mk_th_axiom(get_id(), 1, &lit); TRACEFN("macro expansion yields " << pp_lit(ctx, lit)); - if (has_quantifiers(rhs)) - throw default_exception("quantified formulas in recursive functions are not supported"); } /** @@ -377,6 +381,13 @@ namespace smt { unsigned depth = get_depth(e.m_pred); expr_ref lhs(u().mk_fun_defined(d, args), m); expr_ref rhs = apply_args(depth, vars, args, e.m_cdef->get_rhs()); + if (has_quantifiers(rhs)) { + expr_ref fn(m.mk_fresh_const("rec-eq", m.mk_bool_sort()), m); + expr_ref eq(m.mk_eq(fn, rhs), m); + ctx.assert_expr(eq); + ctx.internalize_assertions(); + rhs = fn; + } literal_vector clause; for (auto & g : e.m_cdef->get_guards()) { expr_ref guard = apply_args(depth, vars, args, g); @@ -394,8 +405,6 @@ namespace smt { std::function fn = [&]() { return clause; }; scoped_trace_stream _tr(*this, fn); ctx.mk_th_axiom(get_id(), clause); - if (has_quantifiers(rhs)) - throw default_exception("quantified formulas in recursive functions are not supported"); } final_check_status theory_recfun::final_check_eh() { diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 9b00c4ced..3db4c4776 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -562,55 +562,66 @@ void theory_seq::mk_decompose(expr* e, expr_ref& head, expr_ref& tail) { /* \brief Check extensionality (for sequences). */ + +bool theory_seq::check_extensionality(expr* e1, enode* n1, enode* n2) { + dependency* dep = nullptr; + expr* o1 = n1->get_expr(); + expr* o2 = n2->get_expr(); + if (o1->get_sort() != o2->get_sort()) + return true; + if (ctx.is_diseq(n1, n2) || m_exclude.contains(o1, o2)) + return true; + expr_ref e2(m); + if (!canonize(n2->get_expr(), dep, e2)) + return false; + m_new_eqs.reset(); + bool change = false; + if (!m_seq_rewrite.reduce_eq(e1, e2, m_new_eqs, change)) { + TRACE("seq", tout << "exclude " << mk_pp(o1, m) << " " << mk_pp(o2, m) << "\n";); + m_exclude.update(o1, o2); + return true; + } + for (auto const& p : m_new_eqs) { + if (m_exclude.contains(p.first, p.second)) { + TRACE("seq", tout << "excluded " << mk_pp(p.first, m) << " " << mk_pp(p.second, m) << "\n";); + return true; + } + } + ctx.assume_eq(n1, n2); + return false; +} + bool theory_seq::check_extensionality() { unsigned sz = get_num_vars(); unsigned_vector seqs; + dependency* dep = nullptr; + expr_ref e1(m); for (unsigned v = 0; v < sz; ++v) { enode* n1 = get_enode(v); expr* o1 = n1->get_expr(); - if (n1 != n1->get_root()) { - continue; - } - if (!seqs.empty() && ctx.is_relevant(n1) && m_util.is_seq(o1) && ctx.is_shared(n1)) { - dependency* dep = nullptr; - expr_ref e1(m); - if (!canonize(o1, dep, e1)) { + // check extensionality for sequence arguments of nth_u + // two occurrences of nth_u(a,x), nth_u(b,y) must induce comparing sequences a, b + // whenever x = y + if (m_util.str.is_nth_u(o1) && n1->is_cgr()) { + auto* r0 = n1->get_arg(0)->get_root(); + auto* r1 = n1->get_arg(1)->get_root(); + if (!canonize(r0->get_expr(), dep, e1)) return false; - } - for (theory_var v : seqs) { - enode* n2 = get_enode(v); - expr* o2 = n2->get_expr(); - if (o1->get_sort() != o2->get_sort()) { - continue; - } - if (ctx.is_diseq(n1, n2) || m_exclude.contains(o1, o2)) { - continue; - } - expr_ref e2(m); - if (!canonize(n2->get_expr(), dep, e2)) { + for (auto* p : r1->get_parents()) + if (p != n1 && p->is_cgr() && m_util.str.is_nth_u(p->get_expr()) && + !check_extensionality(e1, r0, p->get_arg(0)->get_root())) return false; - } - m_new_eqs.reset(); - bool change = false; - if (!m_seq_rewrite.reduce_eq(e1, e2, m_new_eqs, change)) { - TRACE("seq", tout << "exclude " << mk_pp(o1, m) << " " << mk_pp(o2, m) << "\n";); - m_exclude.update(o1, o2); - continue; - } - bool excluded = false; - for (auto const& p : m_new_eqs) { - if (m_exclude.contains(p.first, p.second)) { - TRACE("seq", tout << "excluded " << mk_pp(p.first, m) << " " << mk_pp(p.second, m) << "\n";); - excluded = true; - break; - } - } - if (excluded) { - continue; - } - ctx.assume_eq(n1, n2); + } + if (!n1->is_root()) + continue; + if (!m_util.is_seq(o1)) + continue; + if (!seqs.empty() && ctx.is_relevant(n1) && ctx.is_shared(n1)) { + if (!canonize(o1, dep, e1)) return false; - } + for (theory_var v : seqs) + if (!check_extensionality(e1, n1, get_enode(v))) + return false; } seqs.push_back(v); } @@ -726,7 +737,7 @@ void theory_seq::linearize(dependency* dep, enode_pair_vector& eqs, literal_vect svector assumptions; const_cast(m_dm).linearize(dep, assumptions); for (assumption const& a : assumptions) { - if (a.lit != null_literal) { + if (a.lit != null_literal && a.lit != true_literal) { lits.push_back(a.lit); SASSERT(ctx.get_assignment(a.lit) == l_true); } @@ -743,7 +754,7 @@ bool theory_seq::propagate_lit(dependency* dep, unsigned n, literal const* _lits return false; if (ctx.get_assignment(lit) == l_true) return false; - + literal_vector lits(n, _lits); if (lit == false_literal) { @@ -2368,7 +2379,7 @@ void theory_seq::validate_fmls(enode_pair_vector const& eqs, literal_vector cons theory_var theory_seq::mk_var(enode* n) { expr* o = n->get_expr(); - if (!m_util.is_seq(o) && !m_util.is_re(o)) + if (!m_util.is_seq(o) && !m_util.is_re(o) && !m_util.str.is_nth_u(o)) return null_theory_var; if (is_attached_to_var(n)) @@ -2898,6 +2909,7 @@ bool theory_seq::propagate_eq(dependency* deps, literal_vector const& _lits, exp if (n1->get_root() == n2->get_root()) { return false; } + ctx.mark_as_relevant(n1); ctx.mark_as_relevant(n2); @@ -2979,8 +2991,8 @@ void theory_seq::assign_eh(bool_var v, bool is_true) { m_rewrite(se1); m_rewrite(se2); if (is_true) { - expr_ref f1 = m_sk.mk_indexof_left(se1, se2); - expr_ref f2 = m_sk.mk_indexof_right(se1, se2); + expr_ref f1 = m_sk.mk_contains_left(se1, se2); + expr_ref f2 = m_sk.mk_contains_right(se1, se2); f = mk_concat(f1, se2, f2); propagate_eq(lit, f, e1, true); propagate_eq(lit, mk_len(f), mk_len(e1), false); diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index ad6ef0695..68e9f3762 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -458,6 +458,7 @@ namespace smt { void add_unhandled_expr(expr* e); bool check_extensionality(); + bool check_extensionality(expr* e1, enode* n1, enode* n2); bool check_contains(); bool check_lts(); dependency* m_eq_deps { nullptr }; diff --git a/src/smt/theory_special_relations.cpp b/src/smt/theory_special_relations.cpp index 78d5984e5..b25c2bac8 100644 --- a/src/smt/theory_special_relations.cpp +++ b/src/smt/theory_special_relations.cpp @@ -1146,5 +1146,11 @@ namespace smt { expr* e = ctx.bool_var2expr(a.var()); out << (a.phase() ? "" : "(not ") << mk_pp(e, get_manager()) << (a.phase() ? "" : ")") << "\n"; } - + + + void theory_special_relations::get_specrels(func_decl_set& rels) const { + for (auto [f, r] : m_relations) + rels.insert(m_util.get_relation(r->m_decl)); + } + } diff --git a/src/smt/theory_special_relations.h b/src/smt/theory_special_relations.h index 44eee7395..73e889a5d 100644 --- a/src/smt/theory_special_relations.h +++ b/src/smt/theory_special_relations.h @@ -199,6 +199,8 @@ namespace smt { bool can_propagate() override { return m_can_propagate; } void propagate() override; void display(std::ostream & out) const override; + + void get_specrels(func_decl_set& rels) const; }; } diff --git a/src/smt/user_propagator.h b/src/smt/user_propagator.h index e37dd751d..dcf41c3bb 100644 --- a/src/smt/user_propagator.h +++ b/src/smt/user_propagator.h @@ -48,7 +48,7 @@ namespace smt { void reset() { memset(this, 0, sizeof(*this)); } }; - void* m_user_context; + void* m_user_context = nullptr; solver::push_eh_t m_push_eh; solver::pop_eh_t m_pop_eh; solver::fresh_eh_t m_fresh_eh; @@ -56,13 +56,13 @@ namespace smt { solver::fixed_eh_t m_fixed_eh; solver::eq_eh_t m_eq_eh; solver::eq_eh_t m_diseq_eh; - solver::context_obj* m_api_context { nullptr }; - unsigned m_qhead { 0 }; + solver::context_obj* m_api_context = nullptr; + unsigned m_qhead = 0; uint_set m_fixed; vector m_prop; unsigned_vector m_prop_lim; vector m_id2justification; - unsigned m_num_scopes { 0 }; + unsigned m_num_scopes = 0; literal_vector m_lits; enode_pair_vector m_eqs; stats m_stats; diff --git a/src/solver/assertions/asserted_formulas.cpp b/src/solver/assertions/asserted_formulas.cpp index 9e9b54140..b3be7e8ab 100644 --- a/src/solver/assertions/asserted_formulas.cpp +++ b/src/solver/assertions/asserted_formulas.cpp @@ -213,13 +213,13 @@ void asserted_formulas::force_push() { } void asserted_formulas::pop_scope(unsigned num_scopes) { - if (m_lazy_scopes > 0) { - unsigned n = std::min(num_scopes, m_lazy_scopes); - m_lazy_scopes -= n; - num_scopes -= n; - if (num_scopes == 0) - return; + if (num_scopes <= m_lazy_scopes) { + m_lazy_scopes -= num_scopes; + return; } + num_scopes -= m_lazy_scopes; + m_lazy_scopes = 0; + TRACE("asserted_formulas_scopes", tout << "before pop " << num_scopes << " of " << m_scopes.size() << "\n";); m_bv_sharing.pop_scope(num_scopes); m_macro_manager.pop_scope(num_scopes); diff --git a/src/solver/assertions/asserted_formulas.h b/src/solver/assertions/asserted_formulas.h index 59f83a34f..95848133c 100644 --- a/src/solver/assertions/asserted_formulas.h +++ b/src/solver/assertions/asserted_formulas.h @@ -71,6 +71,7 @@ class asserted_formulas { char const* m_id; public: simplify_fmls(asserted_formulas& af, char const* id): af(af), m(af.m), m_id(id) {} + virtual ~simplify_fmls() = default; char const* id() const { return m_id; } virtual void simplify(justified_expr const& j, expr_ref& n, proof_ref& p) = 0; virtual bool should_apply() const { return true;} diff --git a/src/solver/smt_logics.cpp b/src/solver/smt_logics.cpp index ec1acfdb7..65d3977ab 100644 --- a/src/solver/smt_logics.cpp +++ b/src/solver/smt_logics.cpp @@ -160,6 +160,6 @@ bool smt_logics::logic_has_pb(symbol const& s) { } bool smt_logics::logic_has_datatype(symbol const& s) { - return s == "QF_FD" || s == "QF_UFDT" || logic_is_all(s) || s == "QF_DT"; + return s == "QF_FD" || s == "QF_UFDT" || logic_is_all(s) || s == "QF_DT" || logic_has_horn(s); } diff --git a/src/solver/solver.h b/src/solver/solver.h index 14fd49bce..550105167 100644 --- a/src/solver/solver.h +++ b/src/solver/solver.h @@ -51,7 +51,7 @@ class solver : public check_sat_result { params_ref m_params; symbol m_cancel_backup_file; public: - solver() {} + solver() {} ~solver() override {} /** @@ -241,6 +241,7 @@ public: class propagate_callback { public: + virtual ~propagate_callback() = default; virtual void propagate_cb(unsigned num_fixed, unsigned const* fixed_ids, unsigned num_eqs, unsigned const* eq_lhs, unsigned const* eq_rhs, expr* conseq) = 0; }; class context_obj { diff --git a/src/tactic/fd_solver/smtfd_solver.cpp b/src/tactic/fd_solver/smtfd_solver.cpp index 3e23175ff..c5d67506e 100644 --- a/src/tactic/fd_solver/smtfd_solver.cpp +++ b/src/tactic/fd_solver/smtfd_solver.cpp @@ -509,6 +509,8 @@ namespace smtfd { m_context.add_plugin(this); } + virtual ~theory_plugin() = default; + table& ast2table(ast* f, sort* s) { unsigned idx = 0; if (!m_ast2table.find(f, s, idx)) { diff --git a/src/tactic/smtlogics/qflia_tactic.cpp b/src/tactic/smtlogics/qflia_tactic.cpp index 8673b6380..b8ebbd8a9 100644 --- a/src/tactic/smtlogics/qflia_tactic.cpp +++ b/src/tactic/smtlogics/qflia_tactic.cpp @@ -190,22 +190,14 @@ tactic * mk_preamble_tactic(ast_manager& m) { ctx_simp_p.set_uint("max_depth", 30); ctx_simp_p.set_uint("max_steps", 5000000); - params_ref lhs_p; - lhs_p.set_bool("arith_lhs", true); - - tactic * preamble_st = and_then(mk_simplify_tactic(m), - mk_propagate_values_tactic(m), - using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), - using_params(mk_simplify_tactic(m), pull_ite_p) - ); - - preamble_st = and_then(preamble_st, - mk_solve_eqs_tactic(m), - mk_elim_uncnstr_tactic(m), - using_params(mk_simplify_tactic(m), lhs_p) - ); - - return preamble_st; + return + and_then( + mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + using_params(mk_simplify_tactic(m), pull_ite_p), + mk_solve_eqs_tactic(m), + mk_elim_uncnstr_tactic(m)); } tactic * mk_qflia_tactic(ast_manager & m, params_ref const & p) { @@ -215,23 +207,25 @@ tactic * mk_qflia_tactic(ast_manager & m, params_ref const & p) { main_p.set_bool("blast_distinct", true); main_p.set_uint("blast_distinct_threshold", 128); // main_p.set_bool("push_ite_arith", true); - - - + params_ref quasi_pb_p; quasi_pb_p.set_uint("lia2pb_max_bits", 64); - - tactic * preamble_st = mk_preamble_tactic(m); - tactic * st = using_params(and_then(preamble_st, - or_else(mk_ilp_model_finder_tactic(m), - mk_pb_tactic(m), - and_then(fail_if_not(mk_is_quasi_pb_probe()), - using_params(mk_lia2sat_tactic(m), quasi_pb_p), - mk_fail_if_undecided_tactic()), - mk_bounded_tactic(m), - mk_smt_tactic(m))), - main_p); + params_ref lhs_p; + lhs_p.set_bool("arith_lhs", true); + + tactic* st = using_params( + and_then( + mk_preamble_tactic(m), + using_params(mk_simplify_tactic(m), lhs_p), + or_else(mk_ilp_model_finder_tactic(m), + mk_pb_tactic(m), + and_then(fail_if_not(mk_is_quasi_pb_probe()), + using_params(mk_lia2sat_tactic(m), quasi_pb_p), + mk_fail_if_undecided_tactic()), + mk_bounded_tactic(m), + mk_smt_tactic(m))), + main_p); st->updt_params(p); diff --git a/src/util/small_object_allocator.h b/src/util/small_object_allocator.h index 028c79645..05fe32ef0 100644 --- a/src/util/small_object_allocator.h +++ b/src/util/small_object_allocator.h @@ -21,6 +21,7 @@ Revision History: #include "util/machine.h" #include "util/debug.h" +#include "util/trace.h" class small_object_allocator { static const unsigned CHUNK_SIZE = (8192 - sizeof(void*)*2); diff --git a/src/util/trace.cpp b/src/util/trace.cpp index 5e2930999..d6e6323fc 100644 --- a/src/util/trace.cpp +++ b/src/util/trace.cpp @@ -19,6 +19,25 @@ Revision History: #include "util/trace.h" #include "util/str_hashtable.h" +#ifndef SINGLE_THREAD +#include +#include + +static std::mutex g_verbose_mux; +void verbose_lock() { g_verbose_mux.lock(); } +void verbose_unlock() { g_verbose_mux.unlock(); } + +static std::thread::id g_thread_id = std::this_thread::get_id(); +static bool g_is_threaded = false; + +bool is_threaded() { + if (g_is_threaded) return true; + g_is_threaded = std::this_thread::get_id() != g_thread_id; + return g_is_threaded; +} + +#endif + #ifdef _TRACE std::ofstream tout(".z3-trace"); diff --git a/src/util/trace.h b/src/util/trace.h index fad4707c0..2a63bfaf9 100644 --- a/src/util/trace.h +++ b/src/util/trace.h @@ -21,6 +21,33 @@ Revision History: #include +#ifdef SINGLE_THREAD +# define is_threaded() false +#else +bool is_threaded(); +#endif + +#ifdef SINGLE_THREAD +#define LOCK_CODE(CODE) CODE; +#define THREAD_LOCK(CODE) CODE + +#else +void verbose_lock(); +void verbose_unlock(); + +#define LOCK_CODE(CODE) \ + { \ + verbose_lock(); \ + CODE; \ + verbose_unlock(); \ + } + +#define THREAD_LOCK(CODE) if (is_threaded()) { LOCK_CODE(CODE); } else { CODE; } + +#endif + + + #ifdef _TRACE extern std::ofstream tout; #define TRACE_CODE(CODE) { CODE } ((void) 0 ) @@ -48,10 +75,15 @@ static inline void open_trace() {} static inline void finalize_trace() {} #endif -#define TRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { tout << "-------- [" << TAG << "] " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << " ---------\n"; CODE tout << "------------------------------------------------\n"; tout.flush(); }) +#define TRACEH(TAG) tout << "-------- [" << TAG << "] " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << " ---------\n" +#define TRACEEND tout << "------------------------------------------------\n" +#define TRACEBODY(TAG, CODE) TRACEH(TAG); CODE; TRACEEND; tout.flush() +#define STRACEBODY(CODE) CODE; tout.flush() -#define STRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { CODE tout.flush(); }) +#define TRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { THREAD_LOCK(TRACEBODY(TAG, CODE)); }) -#define SCTRACE(TAG, COND, CODE) TRACE_CODE(if (is_trace_enabled(TAG) && (COND)) { CODE tout.flush(); }) +#define STRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { THREAD_LOCK(STRACEBODY(CODE)); }) -#define CTRACE(TAG, COND, CODE) TRACE_CODE(if (is_trace_enabled(TAG) && (COND)) { tout << "-------- [" << TAG << "] " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << " ---------\n"; CODE tout << "------------------------------------------------\n"; tout.flush(); }) +#define SCTRACE(TAG, COND, CODE) TRACE_CODE(if (is_trace_enabled(TAG) && (COND)) { THREAD_LOCK(STRACEBODY(CODE)); }) + +#define CTRACE(TAG, COND, CODE) TRACE_CODE(if (is_trace_enabled(TAG) && (COND)) { THREAD_LOCK(TRACEBODY(TAG, CODE)); }) diff --git a/src/util/util.cpp b/src/util/util.cpp index 714c947a9..a59b26990 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -19,14 +19,6 @@ Revision History: #include "util/util.h" -#ifndef SINGLE_THREAD -#include -#include - -static std::mutex g_verbose_mux; -void verbose_lock() { g_verbose_mux.lock(); } -void verbose_unlock() { g_verbose_mux.unlock(); } -#endif static unsigned g_verbosity_level = 0; @@ -44,16 +36,6 @@ void set_verbose_stream(std::ostream& str) { g_verbose_stream = &str; } -#ifndef SINGLE_THREAD -static std::thread::id g_thread_id = std::this_thread::get_id(); -static bool g_is_threaded = false; - -bool is_threaded() { - if (g_is_threaded) return true; - g_is_threaded = std::this_thread::get_id() != g_thread_id; - return g_is_threaded; -} -#endif std::ostream& verbose_stream() { return *g_verbose_stream; diff --git a/src/util/util.h b/src/util/util.h index 8d3682943..4cac2123a 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -169,37 +169,11 @@ void set_verbosity_level(unsigned lvl); unsigned get_verbosity_level(); std::ostream& verbose_stream(); void set_verbose_stream(std::ostream& str); -#ifdef SINGLE_THREAD -# define is_threaded() false -#else -bool is_threaded(); -#endif -#define IF_VERBOSE(LVL, CODE) { \ - if (get_verbosity_level() >= LVL) { \ - if (is_threaded()) { \ - LOCK_CODE(CODE); \ - } \ - else { \ - CODE; \ - } \ - } } ((void) 0) +#define IF_VERBOSE(LVL, CODE) { if (get_verbosity_level() >= LVL) { THREAD_LOCK(CODE); } } ((void) 0) -#ifdef SINGLE_THREAD -#define LOCK_CODE(CODE) CODE; -#else -void verbose_lock(); -void verbose_unlock(); - -#define LOCK_CODE(CODE) \ - { \ - verbose_lock(); \ - CODE; \ - verbose_unlock(); \ - } -#endif template struct default_eq {