diff --git a/.github/workflows/wasm-release.yml b/.github/workflows/wasm-release.yml index 376a26684..255de7dc5 100644 --- a/.github/workflows/wasm-release.yml +++ b/.github/workflows/wasm-release.yml @@ -10,7 +10,7 @@ defaults: working-directory: src/api/js env: - EM_VERSION: 3.1.0 + EM_VERSION: 3.1.15 jobs: publish: diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 809fad42e..bd76c8033 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -10,7 +10,7 @@ defaults: working-directory: src/api/js env: - EM_VERSION: 3.1.0 + EM_VERSION: 3.1.15 jobs: check: diff --git a/.gitignore b/.gitignore index 29c49a130..3fe3a3110 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ bld_rel/* bld_dbg_x64/* bld_rel_x64/* .vscode +*build*/** # Auto generated files. config.log config.status diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f3633f0f..4b232f32d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") -project(Z3 VERSION 4.8.18.0 LANGUAGES CXX C) +project(Z3 VERSION 4.9.2.0 LANGUAGES CXX C) ################################################################################ # Project version @@ -160,7 +160,7 @@ list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:_EXTERNAL_RELEAS ################################################################################ # Find Python ################################################################################ -find_package(PythonInterp REQUIRED) +find_package(PythonInterp 3 REQUIRED) message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}") ################################################################################ @@ -230,7 +230,7 @@ else() message(FATAL_ERROR "Platform \"${CMAKE_SYSTEM_NAME}\" not recognised") endif() -list(APPEND Z3_COMPONENT_EXTRA_INCLUDE_DIRS +list(APPEND Z3_COMPONENT_EXTRA_INCLUDE_DIRS "${PROJECT_BINARY_DIR}/src" "${PROJECT_SOURCE_DIR}/src" ) @@ -293,8 +293,8 @@ if ((TARGET_ARCHITECTURE STREQUAL "x86_64") OR (TARGET_ARCHITECTURE STREQUAL "i6 set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") - # Intel's compiler requires linking with libiomp5 - list(APPEND Z3_DEPENDENT_LIBS "iomp5") + # Intel's compiler requires linking with libiomp5 + list(APPEND Z3_DEPENDENT_LIBS "iomp5") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(SSE_FLAGS "/arch:SSE2") else() @@ -624,7 +624,7 @@ install( ################################################################################ # Examples ################################################################################ -cmake_dependent_option(Z3_ENABLE_EXAMPLE_TARGETS +cmake_dependent_option(Z3_ENABLE_EXAMPLE_TARGETS "Build Z3 api examples" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) if (Z3_ENABLE_EXAMPLE_TARGETS) diff --git a/RELEASE_NOTES b/RELEASE_NOTES.md similarity index 91% rename from RELEASE_NOTES rename to RELEASE_NOTES.md index e9c5c1fd7..4b2834640 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ RELEASE NOTES -Version 4.8.next +Version 4.next ================ - Planned features - sat.euf @@ -10,6 +10,73 @@ Version 4.8.next - native word level bit-vector solving. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. +Version 4.10.0 +============== +- Added API Z3_enable_concurrent_dec_ref to be set by interfaces that + use concurrent GC to manage reference counts. This feature is integrated + with the OCaml bindings and fixes a regression introduced when OCaml + transitioned to concurrent GC. Use of this feature for .Net and Java + bindings is not integrated for this release. They use external queues + that are unnecessarily complicated. +- Added pre-declared abstract datatype declarations to the context so + that Z3_eval_smtlib2_string works with List examples. +- Fixed Java linking for MacOS Arm64. +- Added missing callback handlers in tactics for user-propagator, + Thanks to Clemens Eisenhofer +- Tuning to Grobner arithmetic reasoning for smt.arith.solver=6 + (currently the default in most cases). The check for consistency + modulo multiplication was updated in the following way: + - polynomial equalities are extracted from Simplex tableau rows using + a cone of influence algorithm. Rows where the basic variables were + unbounded were previously included. Following the legacy implementation + such rows are not included when building polynomial equations. + - equations are pre-solved if they are linear and can be split + into two groups one containing a single variable that has a + lower (upper) bound, the other with more than two variables + with upper (lower) bounds. This avoids losing bounds information + during completion. + - After (partial) completion, perform constant propagation for equalities + of the form x = 0 + - After (partial) completion, perform factorization for factors of the + form x*y*p = 0 where x, are variables, p is linear. + + +Version 4.9.1 +============= +- Bugfix release to ensure npm package works + +Version 4.9.0 +============= +- Native M1 (Mac ARM64) binaries and pypi distribution. + - thanks to Isabel Garcia Contreras and Arie Gurfinkel for testing and fixes +- API for incremental parsing of assertions. + A description of the feature is given by example here: https://github.com/Z3Prover/z3/commit/815518dc026e900392bf0d08ed859e5ec42d1e43 + It also allows incrementality at the level of adding assertions to the + solver object. +- Fold/map for sequences: + https://microsoft.github.io/z3guide/docs/guide/Sequences#map-and-fold + At this point these functions are only exposed over the SMTLIB2 interface (and not programmatic API) + maxdiff/mindiff on arrays are more likely to be deprecated +- User Propagator: + - Add functions and callbacks for external control over branching thanks to Clemens Eisenhofer + - A functioning dotnet API for the User Propagator + https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/UserPropagator.cs +- Java Script API + - higher level object wrappers are available thanks to Kevin Gibbons and Olaf Tomalka +- Totalizers and RC2 + - The MaxSAT engine now allows to run RC2 with totalizer encoding. + Totalizers are on by default as preliminary tests suggest this solves already 10% more problems on + standard benchmarks. The option opt.rc2.totalizer (which by default is true) is used to control whether to use + totalizer encoding or built-in cardinality constraints. + The default engine is still maxres, so you have to set opt.maxsat_engine=rc2 to + enable the rc2 option at this point. The engines maxres-bin and rc2bin are experimental should not be used + (they are inferior to default options). +- Incremental constraints during optimization set option opt.incremental = true + - The interface `Z3_optimize_register_model_eh` allows to monitor incremental results during optimization. + It is now possible to also add constraints to the optimization context during search. + You have to set the option opt.incremental=true to be able to add constraints. The option + disables some pre-processing functionality that removes variables from the constraints. + Version 4.8.17 ============== - fix breaking bug in python interface for user propagator pop @@ -17,6 +84,29 @@ Version 4.8.17 - initial support for nested algebraic datatypes with sequences - initiate map/fold operators on sequences - full integration for next releases - initiate maxdiff/mindiff on arrays - full integration for next releases + +Examples: + +``` +(declare-sort Expr) +(declare-sort Var) +(declare-datatypes ((Stmt 0)) + (((Assignment (lval Var) (rval Expr)) + (If (cond Expr) (th Stmt) (el Stmt)) + (Seq (stmts (Seq Stmt)))))) + +(declare-const s Stmt) +(declare-const t Stmt) + +(assert ((_ is Seq) t)) +(assert ((_ is Seq) s)) +(assert (= s (seq.nth (stmts t) 2))) +(assert (>= (seq.len (stmts s)) 5)) +(check-sat) +(get-model) +(assert (= s (Seq (seq.unit s)))) +(check-sat) +``` Version 4.8.16 ============== @@ -964,7 +1054,7 @@ The following bugs are fixed in this release: - Non-termination problem associated with option LOOKAHEAD=true. It gets set for QF_UF in auto-configuration mode. - Thanks to Pierre-Christophe Bué. + Thanks to Pierre-Christophe Bué. - Incorrect axioms created for injective functions. Thanks to Sascha Boehme. @@ -986,7 +1076,7 @@ Version 2.6 =========== This release fixes a few bugs. -Thanks to Marko Kääramees for reporting a bug in the strong context simplifier and +Thanks to Marko Kääramees for reporting a bug in the strong context simplifier and to Josh Berdine. This release also introduces some new preprocessing features: @@ -1018,7 +1108,7 @@ This release introduces the following features: Z3_update_param_value in the C API. This is particularly useful for turning the strong context simplifier on and off. -It also fixes bugs reported by Enric Rodríguez Carbonell, +It also fixes bugs reported by Enric Rodríguez Carbonell, Nuno Lopes, Josh Berdine, Ethan Jackson, Rob Quigley and Lucas Cordeiro. @@ -1085,7 +1175,7 @@ Version 2.1 =========== This is a bug fix release. -Many thanks to Robert Brummayer, Carine Pascal, François Remy, +Many thanks to Robert Brummayer, Carine Pascal, François Remy, Rajesh K Karmani, Roberto Lublinerman and numerous others for their feedback and bug reports. diff --git a/doc/mk_api_doc.py b/doc/mk_api_doc.py index 7ca47b89a..670634e07 100644 --- a/doc/mk_api_doc.py +++ b/doc/mk_api_doc.py @@ -335,6 +335,7 @@ try: for modulename in ( 'z3', + 'z3.z3', 'z3.z3consts', 'z3.z3core', 'z3.z3num', diff --git a/doc/website.dox.in b/doc/website.dox.in index 5d084550c..5249f2ae0 100644 --- a/doc/website.dox.in +++ b/doc/website.dox.in @@ -8,6 +8,5 @@ This website hosts the automatically generated documentation for the Z3 APIs. - - \ref @C_API@ - - \ref @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ + - \ref @C_API@ @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ */ diff --git a/examples/c++/example.cpp b/examples/c++/example.cpp index 0217c5cca..eb6d2c19b 100644 --- a/examples/c++/example.cpp +++ b/examples/c++/example.cpp @@ -929,8 +929,8 @@ void enum_sort_example() { sort s = ctx.enumeration_sort("enumT", 3, enum_names, enum_consts, enum_testers); // enum_consts[0] is a func_decl of arity 0. // we convert it to an expression using the operator() - expr a = enum_consts[0](); - expr b = enum_consts[1](); + expr a = enum_consts[0u](); + expr b = enum_consts[1u](); expr x = ctx.constant("x", s); expr test = (x==a) && (x==b); std::cout << "1: " << test << std::endl; @@ -956,6 +956,55 @@ void tuple_example() { std::cout << pair2 << "\n"; } +void datatype_example() { + std::cout << "datatype example\n"; + context ctx; + constructors cs(ctx); + symbol ilist = ctx.str_symbol("ilist"); + symbol accs[2] = { ctx.str_symbol("hd"), ctx.str_symbol("tl") }; + sort sorts[2] = { ctx.int_sort(), ctx.datatype_sort(ilist) }; + cs.add(ctx.str_symbol("nil"), ctx.str_symbol("is-nil"), 0, nullptr, nullptr); + cs.add(ctx.str_symbol("cons"), ctx.str_symbol("is-cons"), 2, accs, sorts); + sort ls = ctx.datatype(ilist, cs); + std::cout << ls << "\n"; + func_decl nil(ctx), is_nil(ctx); + func_decl_vector nil_acc(ctx); + cs.query(0, nil, is_nil, nil_acc); + func_decl cons(ctx), is_cons(ctx); + func_decl_vector cons_acc(ctx); + cs.query(1, cons, is_cons, cons_acc); + std::cout << nil << " " << is_nil << " " << nil_acc << "\n"; + std::cout << cons << " " << is_cons << " " << cons_acc << "\n"; + + symbol tree = ctx.str_symbol("tree"); + symbol tlist = ctx.str_symbol("tree_list"); + symbol accs1[2] = { ctx.str_symbol("left"), ctx.str_symbol("right") }; + symbol accs2[2] = { ctx.str_symbol("hd"), ctx.str_symbol("tail") }; + sort sorts1[2] = { ctx.datatype_sort(tlist), ctx.datatype_sort(tlist) }; + sort sorts2[2] = { ctx.int_sort(), ctx.datatype_sort(tree) }; + constructors cs1(ctx), cs2(ctx); + cs1.add(ctx.str_symbol("tnil"), ctx.str_symbol("is-tnil"), 0, nullptr, nullptr); + cs1.add(ctx.str_symbol("tnode"), ctx.str_symbol("is-tnode"), 2, accs1, sorts1); + constructor_list cl1(cs1); + cs2.add(ctx.str_symbol("lnil"), ctx.str_symbol("is-lnil"), 0, nullptr, nullptr); + cs2.add(ctx.str_symbol("lcons"), ctx.str_symbol("is-lcons"), 2, accs2, sorts2); + constructor_list cl2(cs2); + symbol names[2] = { tree, tlist }; + constructor_list* cl[2] = { &cl1, &cl2 }; + sort_vector dsorts = ctx.datatypes(2, names, cl); + std::cout << dsorts << "\n"; + cs1.query(0, nil, is_nil, nil_acc); + cs1.query(1, cons, is_cons, cons_acc); + std::cout << nil << " " << is_nil << " " << nil_acc << "\n"; + std::cout << cons << " " << is_cons << " " << cons_acc << "\n"; + + cs2.query(0, nil, is_nil, nil_acc); + cs2.query(1, cons, is_cons, cons_acc); + std::cout << nil << " " << is_nil << " " << nil_acc << "\n"; + std::cout << cons << " " << is_cons << " " << cons_acc << "\n"; + +} + void expr_vector_example() { std::cout << "expr_vector example\n"; context c; @@ -1343,6 +1392,7 @@ int main() { incremental_example3(); std::cout << "\n"; enum_sort_example(); std::cout << "\n"; tuple_example(); std::cout << "\n"; + datatype_example(); std::cout << "\n"; expr_vector_example(); std::cout << "\n"; exists_expr_vector_example(); std::cout << "\n"; substitute_example(); std::cout << "\n"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4c22d5b85 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "requires": { + "tslib": "^2.3.1" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "z3-solver": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/z3-solver/-/z3-solver-4.9.0.tgz", + "integrity": "sha512-clSV0uyHsfrO84pSbHxoqvmd5HgSG4CoSJG2f8U65hBVylbV6p/0svctQWee9W2fWo0IsxHYRjxz2Z85GT0LAA==", + "requires": { + "async-mutex": "^0.3.2" + } + } + } +} diff --git a/scripts/jsdoctest.yml b/scripts/jsdoctest.yml new file mode 100644 index 000000000..1a3a5286f --- /dev/null +++ b/scripts/jsdoctest.yml @@ -0,0 +1,49 @@ +variables: + + Major: '4' + Minor: '9' + Patch: '2' + NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) + +stages: +- stage: Build + jobs: + + - job: UbuntuDoc + displayName: "Ubuntu Doc build" + pool: + vmImage: "Ubuntu-latest" + steps: +# TODO setup emscripten with no-install, then run + - script: npm --prefix=src/api/js ci + - script: npm --prefix=src/api/js run build:ts + + - 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 + - script: sudo apt-get install doxygen + - script: sudo apt-get install graphviz + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 examples + make -j3 test-z3 + cd .. + - script: | + set -e + eval `opam config env` + cd doc + python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 --js + mkdir api/html/ml + ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli + cd .. + - script: zip -r z3doc.zip doc/api + - script: cp z3doc.zip $(Build.ArtifactStagingDirectory)/. + - task: PublishPipelineArtifact@0 + inputs: + artifactName: 'UbuntuDoc' + targetPath: $(Build.ArtifactStagingDirectory) + diff --git a/scripts/mk_project.py b/scripts/mk_project.py index b052ac2cc..919e18a4a 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -8,7 +8,7 @@ from mk_util import * def init_version(): - set_version(4, 8, 18, 0) + set_version(4, 9, 2, 0) # Z3 Project definition def init_project_def(): diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 13c01567d..54242dfe6 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -1831,7 +1831,7 @@ class JavaDLLComponent(Component): out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) %s$(OBJ_EXT) libz3$(LIB_EXT)\n' % os.path.join('api', 'java', 'Native')) else: - out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) %s$(OBJ_EXT) libz3$(SO_EXT)\n' % + out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) $(SLINK_EXTRA_FLAGS) %s$(OBJ_EXT) libz3$(SO_EXT)\n' % os.path.join('api', 'java', 'Native')) out.write('%s.jar: libz3java$(SO_EXT) ' % self.package_name) deps = '' diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index e7ccb4731..fd93d4236 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -1,8 +1,8 @@ variables: Major: '4' - Minor: '8' - Patch: '18' + Minor: '9' + Patch: '2' NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) stages: diff --git a/scripts/release.yml b/scripts/release.yml index 20361e9ca..419d4bc4b 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.8.18' + ReleaseVersion: '4.9.2' stages: @@ -44,25 +44,18 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) - job: MacBuildArm64 - displayName: "macOS Build" + displayName: "macOS ARM64 Build" pool: vmImage: "macOS-latest" steps: - - task: PythonScript@0 - displayName: Build + - script: python scripts/mk_unix_dist.py --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --arch=arm64 --os=osx-11.0 + - script: git clone https://github.com/z3prover/z3test z3test + - script: cp dist/*.zip $(Build.ArtifactStagingDirectory)/. + - task: PublishPipelineArtifact@1 inputs: - scriptSource: 'filepath' - scriptPath: scripts/mk_unix_dist.py - arguments: --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --nojava --arch=arm64 --os=osx-11.0 - - task: CopyFiles@2 - inputs: - sourceFolder: dist - contents: '*.zip' - targetFolder: $(Build.ArtifactStagingDirectory) - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'macOSBuildArm64' + artifactName: 'MacArm64' targetPath: $(Build.ArtifactStagingDirectory) + - job: UbuntuBuild @@ -130,22 +123,14 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) - job: LinuxBuilds - strategy: - matrix: - manyLinux: - name: ManyLinux - image: "quay.io/pypa/manylinux2010_x86_64:latest" - python: "/opt/python/cp37-cp37m/bin/python" - muslLinux: - name: MuslLinux - image: "quay.io/pypa/musllinux_1_1_x86_64:latest" - python: "/opt/python/cp310-cp310/bin/python" - displayName: "$(name) build" + displayName: "ManyLinux build" + variables: + name: ManyLinux + image: "quay.io/pypa/manylinux2010_x86_64:latest" + python: "/opt/python/cp37-cp37m/bin/python" pool: vmImage: "ubuntu-latest" - container: $(image) - variables: - python: $(python) + container: "quay.io/pypa/manylinux2010_x86_64:latest" steps: - task: PythonScript@0 displayName: Build @@ -170,7 +155,7 @@ stages: targetFolder: $(Build.ArtifactStagingDirectory) - task: PublishPipelineArtifact@0 inputs: - artifactName: '$(name)Build' + artifactName: 'ManyLinuxBuild' targetPath: $(Build.ArtifactStagingDirectory) - template: build-win-signed.yml @@ -405,16 +390,16 @@ stages: inputs: artifact: 'macOSBuild' path: $(Agent.TempDirectory) + - task: DownloadPipelineArtifact@2 + displayName: 'Download macOS Arm64 Build' + inputs: + artifact: 'MacArm64' + path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download ManyLinux Build' inputs: artifact: 'ManyLinuxBuild' path: $(Agent.TempDirectory) - - task: DownloadPipelineArtifact@2 - displayName: 'Download MuslLinux Build' - inputs: - artifact: 'MuslLinuxBuild' - path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download Win32 Build' inputs: @@ -425,19 +410,17 @@ stages: inputs: artifact: 'WindowsBuild-x64' path: $(Agent.TempDirectory) - - script: cd $(Agent.TempDirectory); mkdir osx-bin; cd osx-bin; unzip ../*osx*.zip + - script: cd $(Agent.TempDirectory); mkdir osx-x64-bin; cd osx-x64-bin; unzip ../*x64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir osx-arm64-bin; cd osx-arm64-bin; unzip ../*arm64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir libc-bin; cd libc-bin; unzip ../*glibc*.zip - - script: cd $(Agent.TempDirectory); mkdir musl-bin; cd musl-bin; unzip ../*-linux.zip - script: cd $(Agent.TempDirectory); mkdir win32-bin; cd win32-bin; unzip ../*x86-win*.zip - script: cd $(Agent.TempDirectory); mkdir win64-bin; cd win64-bin; unzip ../*x64-win*.zip - script: python3 -m pip install --user -U setuptools wheel - script: cd src/api/python; python3 setup.py sdist # take a look at this PREMIUM HACK I came up with to get around the fact that the azure variable syntax overloads the bash syntax for subshells - - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel + - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-x64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-arm64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/libc-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - - script: cd src/api/python; echo $(Agent.TempDirectory)/musl-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/win32-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/win64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - task: PublishPipelineArtifact@0 @@ -473,7 +456,7 @@ stages: - task: DownloadPipelineArtifact@2 displayName: 'Download macOSArm64 Build' inputs: - artifact: 'macOSBuildArm64' + artifact: 'MacArm64' path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download Win32 Build' @@ -513,7 +496,7 @@ stages: releaseNotes: '$(ReleaseVersion) release' assets: '$(Agent.TempDirectory)/*.*' isDraft: true - isPreRelease: false + isPreRelease: true # Enable on release (after fixing Nuget key) - job: NuGetPublish diff --git a/scripts/update_api.py b/scripts/update_api.py index f5d935514..8c6275c56 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -91,7 +91,7 @@ Type2Dotnet = { VOID : 'void', VOID_PTR : 'IntPtr', INT : 'int', UINT : 'uint', # Mapping to ML types -Type2ML = { VOID : 'unit', VOID_PTR : 'ptr', INT : 'int', UINT : 'int', INT64 : 'int', UINT64 : 'int', DOUBLE : 'float', +Type2ML = { VOID : 'unit', VOID_PTR : 'ptr', INT : 'int', UINT : 'int', INT64 : 'int64', UINT64 : 'int64', DOUBLE : 'float', FLOAT : 'float', STRING : 'string', STRING_PTR : 'char**', BOOL : 'bool', SYMBOL : 'z3_symbol', PRINT_MODE : 'int', ERROR_CODE : 'int', CHAR : 'char', CHAR_PTR : 'string', LBOOL : 'int' } @@ -254,8 +254,10 @@ def param2pystr(p): def param2ml(p): k = param_kind(p) if k == OUT: - if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL or param_type(p) == INT64 or param_type(p) == UINT64: + if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL: return "int" + elif param_type(p) == INT64 or param_type(p) == UINT64: + return "int64" elif param_type(p) == STRING: return "string" else: @@ -689,6 +691,7 @@ def mk_java(java_src, java_dir, package_name): java_native.write('}\n') java_wrapper = open(java_wrapperf, 'w') pkg_str = package_name.replace('.', '_') + java_wrapper.write("// Automatically generated file\n") with open(java_src + "/NativeStatic.txt") as ins: for line in ins: java_wrapper.write(line) @@ -1251,9 +1254,9 @@ def ml_unwrap(t, ts, s): elif t == UINT: return '(' + ts + ') Unsigned_int_val(' + s + ')' elif t == INT64: - return '(' + ts + ') Long_val(' + s + ')' + return '(' + ts + ') Int64_val(' + s + ')' elif t == UINT64: - return '(' + ts + ') Unsigned_long_val(' + s + ')' + return '(' + ts + ') Int64_val(' + s + ')' elif t == DOUBLE: return '(' + ts + ') Double_val(' + s + ')' elif ml_has_plus_type(ts): @@ -1270,7 +1273,7 @@ def ml_set_wrap(t, d, n): elif t == INT or t == UINT or t == PRINT_MODE or t == ERROR_CODE or t == LBOOL: return d + ' = Val_int(' + n + ');' elif t == INT64 or t == UINT64: - return d + ' = Val_long(' + n + ');' + return d + ' = caml_copy_int64(' + n + ');' elif t == DOUBLE: return d + '= caml_copy_double(' + n + ');' elif t == STRING: diff --git a/src/ackermannization/ackr_model_converter.cpp b/src/ackermannization/ackr_model_converter.cpp index a5b2630c7..78fe6bef0 100644 --- a/src/ackermannization/ackr_model_converter.cpp +++ b/src/ackermannization/ackr_model_converter.cpp @@ -46,11 +46,15 @@ public: void operator()(model_ref & md) override { TRACE("ackermannize", tout << (fixed_model? "fixed" : "nonfixed") << "\n";); - SASSERT(!fixed_model || md.get() == 0 || (!md->get_num_constants() && !md->get_num_functions())); - model_ref& old_model = fixed_model ? abstr_model : md; - SASSERT(old_model.get()); - model * new_model = alloc(model, m); - convert(old_model.get(), new_model); + CTRACE("ackermannize", md, tout << *md << "\n"); + CTRACE("ackermannize", fixed_model, tout << *abstr_model << "\n"); + + model* new_model = alloc(model, m); + + if (abstr_model) + convert(abstr_model.get(), new_model); + if (md) + convert(md.get(), new_model); md = new_model; } diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 3ba279dca..a16341955 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -35,11 +35,12 @@ namespace api { object::object(context& c): m_ref_count(0), m_context(c) { this->m_id = m_context.add_object(this); } - void object::inc_ref() { m_ref_count++; } + void object::inc_ref() { ++m_ref_count; } - void object::dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) m_context.del_object(this); } + void object::dec_ref() { SASSERT(m_ref_count > 0); if (--m_ref_count == 0) m_context.del_object(this); } unsigned context::add_object(api::object* o) { + flush_objects(); unsigned id = m_allocated_objects.size(); if (!m_free_object_ids.empty()) { id = m_free_object_ids.back(); @@ -50,9 +51,52 @@ namespace api { } void context::del_object(api::object* o) { - m_free_object_ids.push_back(o->id()); - m_allocated_objects.remove(o->id()); - dealloc(o); +#ifndef SINGLE_THREAD + if (m_concurrent_dec_ref) { + lock_guard lock(m_mux); + m_objects_to_flush.push_back(o); + } + else +#endif + { + m_free_object_ids.push_back(o->id()); + m_allocated_objects.remove(o->id()); + dealloc(o); + } + } + + void context::dec_ref(ast* a) { +#ifndef SINGLE_THREAD + if (m_concurrent_dec_ref) { + lock_guard lock(m_mux); + m_asts_to_flush.push_back(a); + } + else +#endif + m().dec_ref(a); + } + + void context::flush_objects() { +#ifndef SINGLE_THREAD + if (!m_concurrent_dec_ref) + return; + { + lock_guard lock(m_mux); + if (m_asts_to_flush.empty() && m_objects_to_flush.empty()) + return; + m_asts_to_flush2.swap(m_asts_to_flush); + m_objects_to_flush2.swap(m_objects_to_flush); + } + for (ast* a : m_asts_to_flush2) + m().dec_ref(a); + for (auto* o : m_objects_to_flush2) { + m_free_object_ids.push_back(o->id()); + m_allocated_objects.remove(o->id()); + dealloc(o); + } + m_objects_to_flush2.reset(); + m_asts_to_flush2.reset(); +#endif } static void default_error_handler(Z3_context ctx, Z3_error_code c) { @@ -106,6 +150,7 @@ namespace api { context::~context() { m_last_obj = nullptr; + flush_objects(); for (auto& kv : m_allocated_objects) { api::object* val = kv.m_value; DEBUG_CODE(warning_msg("Uncollected memory: %d: %s", kv.m_key, typeid(*val).name());); @@ -356,6 +401,13 @@ extern "C" { Z3_CATCH; } + void Z3_API Z3_enable_concurrent_dec_ref(Z3_context c) { + Z3_TRY; + LOG_Z3_enable_concurrent_dec_ref(c); + mk_c(c)->enable_concurrent_dec_ref(); + Z3_CATCH; + } + void Z3_API Z3_toggle_warning_messages(bool enabled) { LOG_Z3_toggle_warning_messages(enabled); enable_warning_messages(enabled != 0); @@ -365,6 +417,7 @@ extern "C" { Z3_TRY; LOG_Z3_inc_ref(c, a); RESET_ERROR_CODE(); + mk_c(c)->flush_objects(); mk_c(c)->m().inc_ref(to_ast(a)); Z3_CATCH; } @@ -379,7 +432,7 @@ extern "C" { return; } if (a) { - mk_c(c)->m().dec_ref(to_ast(a)); + mk_c(c)->dec_ref(to_ast(a)); } Z3_CATCH; } diff --git a/src/api/api_context.h b/src/api/api_context.h index 41c797163..182958b1e 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -75,6 +75,9 @@ namespace api { struct add_plugins { add_plugins(ast_manager & m); }; ast_context_params m_params; bool m_user_ref_count; //!< if true, the user is responsible for managing reference counters. +#ifndef SINGLE_THREAD + bool m_concurrent_dec_ref = false; +#endif scoped_ptr m_manager; scoped_ptr m_cmd; add_plugins m_plugins; @@ -91,8 +94,12 @@ namespace api { smt_params m_fparams; // ------------------------------- - ast_ref_vector m_ast_trail; +#ifndef SINGLE_THREAD + ptr_vector m_asts_to_flush, m_asts_to_flush2; + ptr_vector m_objects_to_flush, m_objects_to_flush2; +#endif + ast_ref_vector m_ast_trail; ref m_last_obj; //!< reference to the last API object returned by the APIs u_map m_allocated_objects; // !< table containing current set of allocated API objects unsigned_vector m_free_object_ids; // !< free list of identifiers available for allocated objects. @@ -169,9 +176,18 @@ namespace api { void set_error_code(Z3_error_code err, char const* opt_msg); void set_error_code(Z3_error_code err, std::string &&opt_msg); void set_error_handler(Z3_error_handler h) { m_error_handler = h; } - + + void enable_concurrent_dec_ref() { +#ifdef SINGLE_THREAD + set_error_code(Z3_EXCEPTION, "Can't use concurrent features with a single-thread build"); +#else + m_concurrent_dec_ref = true; +#endif + } unsigned add_object(api::object* o); void del_object(api::object* o); + void dec_ref(ast* a); + void flush_objects(); Z3_ast_print_mode get_print_mode() const { return m_print_mode; } void set_print_mode(Z3_ast_print_mode m) { m_print_mode = m; } diff --git a/src/api/api_parsers.cpp b/src/api/api_parsers.cpp index 1c5afb34f..6355642fc 100644 --- a/src/api/api_parsers.cpp +++ b/src/api/api_parsers.cpp @@ -35,6 +35,136 @@ extern "C" { // --------------- // Support for SMTLIB2 + struct Z3_parser_context_ref : public api::object { + scoped_ptr ctx; + + Z3_parser_context_ref(api::context& c): api::object(c) { + ast_manager& m = c.m(); + ctx = alloc(cmd_context, false, &(m)); + install_dl_cmds(*ctx.get()); + install_opt_cmds(*ctx.get()); + install_smt2_extra_cmds(*ctx.get()); + ctx->register_plist(); + ctx->set_ignore_check(true); + } + + ~Z3_parser_context_ref() override {} + }; + + inline Z3_parser_context_ref * to_parser_context(Z3_parser_context pc) { return reinterpret_cast(pc); } + inline Z3_parser_context of_parser_context(Z3_parser_context_ref* pc) { return reinterpret_cast(pc); } + + Z3_parser_context Z3_API Z3_mk_parser_context(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_parser_context(c); + RESET_ERROR_CODE(); + Z3_parser_context_ref * pc = alloc(Z3_parser_context_ref, *mk_c(c)); + mk_c(c)->save_object(pc); + Z3_parser_context r = of_parser_context(pc); + RETURN_Z3(r); + Z3_CATCH_RETURN(nullptr); + } + + void Z3_API Z3_parser_context_inc_ref(Z3_context c, Z3_parser_context pc) { + Z3_TRY; + LOG_Z3_parser_context_inc_ref(c, pc); + RESET_ERROR_CODE(); + to_parser_context(pc)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_parser_context_dec_ref(Z3_context c, Z3_parser_context pc) { + Z3_TRY; + LOG_Z3_parser_context_dec_ref(c, pc); + RESET_ERROR_CODE(); + to_parser_context(pc)->dec_ref(); + Z3_CATCH; + } + + static void insert_datatype(ast_manager& m, scoped_ptr& ctx, sort* srt) { + datatype_util dt(m); + if (!dt.is_datatype(srt)) + return; + + for (func_decl * c : *dt.get_datatype_constructors(srt)) { + ctx->insert(c->get_name(), c); + func_decl * r = dt.get_constructor_recognizer(c); + ctx->insert(r->get_name(), r); + for (func_decl * a : *dt.get_constructor_accessors(c)) { + ctx->insert(a->get_name(), a); + } + } + } + + static void insert_sort(ast_manager& m, scoped_ptr& ctx, symbol const& name, sort* srt) { + if (ctx->find_psort_decl(name)) + return; + psort* ps = ctx->pm().mk_psort_cnst(srt); + ctx->insert(ctx->pm().mk_psort_user_decl(0, name, ps)); + insert_datatype(m, ctx, srt); + } + + void Z3_API Z3_parser_context_add_sort(Z3_context c, Z3_parser_context pc, Z3_sort s) { + Z3_TRY; + LOG_Z3_parser_context_add_sort(c, pc, s); + RESET_ERROR_CODE(); + auto& ctx = to_parser_context(pc)->ctx; + sort* srt = to_sort(s); + symbol name = srt->get_name(); + insert_sort(mk_c(c)->m(), ctx, name, srt); + Z3_CATCH; + } + + void Z3_API Z3_parser_context_add_decl(Z3_context c, Z3_parser_context pc, Z3_func_decl f) { + Z3_TRY; + LOG_Z3_parser_context_add_decl(c, pc, f); + RESET_ERROR_CODE(); + auto& ctx = *to_parser_context(pc)->ctx; + func_decl* fn = to_func_decl(f); + symbol name = fn->get_name(); + ctx.insert(name, fn); + Z3_CATCH; + } + + Z3_ast_vector Z3_parser_context_parse_stream(Z3_context c, scoped_ptr& ctx, bool owned, std::istream& is) { + Z3_TRY; + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); + mk_c(c)->save_object(v); + std::stringstream errstrm; + ctx->set_regular_stream(errstrm); + try { + if (!parse_smt2_commands(*ctx, is)) { + if (owned) + ctx = nullptr; + SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); + return of_ast_vector(v); + } + } + catch (z3_exception& e) { + if (owned) + ctx = nullptr; + errstrm << e.msg(); + SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); + return of_ast_vector(v); + } + for (expr* e : ctx->tracked_assertions()) + v->m_ast_vector.push_back(e); + ctx->reset_tracked_assertions(); + return of_ast_vector(v); + Z3_CATCH_RETURN(nullptr); + } + + Z3_ast_vector Z3_API Z3_parser_context_from_string(Z3_context c, Z3_parser_context pc, Z3_string str) { + Z3_TRY; + LOG_Z3_parser_context_from_string(c, pc, str); + std::string s(str); + std::istringstream is(s); + auto& ctx = to_parser_context(pc)->ctx; + Z3_ast_vector r = Z3_parser_context_parse_stream(c, ctx, false, is); + RETURN_Z3(r); + Z3_CATCH_RETURN(nullptr); + } + Z3_ast_vector parse_smtlib2_stream(bool exec, Z3_context c, std::istream& is, unsigned num_sorts, Z3_symbol const _sort_names[], @@ -48,70 +178,16 @@ extern "C" { install_dl_cmds(*ctx.get()); install_opt_cmds(*ctx.get()); install_smt2_extra_cmds(*ctx.get()); - ctx->register_plist(); ctx->set_ignore_check(true); - Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), m); - - vector sort_names; - ptr_vector sorts; - for (unsigned i = 0; i < num_sorts; ++i) { - sorts.push_back(to_sort(_sorts[i])); - sort_names.push_back(to_symbol(_sort_names[i])); - } - mk_c(c)->save_object(v); - for (unsigned i = 0; i < num_decls; ++i) { - func_decl* d = to_func_decl(decls[i]); - ctx->insert(to_symbol(decl_names[i]), d); - sort_names.push_back(d->get_range()->get_name()); - sorts.push_back(d->get_range()); - for (sort* s : *d) { - sort_names.push_back(s->get_name()); - sorts.push_back(s); - } - } - datatype_util dt(m); - for (unsigned i = 0; i < num_sorts; ++i) { - sort* srt = sorts[i]; - symbol name = sort_names[i]; - if (ctx->find_psort_decl(name)) { - continue; - } - psort* ps = ctx->pm().mk_psort_cnst(srt); - ctx->insert(ctx->pm().mk_psort_user_decl(0, name, ps)); - if (!dt.is_datatype(srt)) { - continue; - } + for (unsigned i = 0; i < num_decls; ++i) + ctx->insert(to_symbol(decl_names[i]), to_func_decl(decls[i])); - for (func_decl * c : *dt.get_datatype_constructors(srt)) { - ctx->insert(c->get_name(), c); - func_decl * r = dt.get_constructor_recognizer(c); - ctx->insert(r->get_name(), r); - for (func_decl * a : *dt.get_constructor_accessors(c)) { - ctx->insert(a->get_name(), a); - } - } - } - std::stringstream errstrm; - ctx->set_regular_stream(errstrm); - try { - if (!parse_smt2_commands(*ctx.get(), is)) { - ctx = nullptr; - SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); - return of_ast_vector(v); - } - } - catch (z3_exception& e) { - errstrm << e.msg(); - ctx = nullptr; - SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); - return of_ast_vector(v); - } - for (expr* e : ctx->tracked_assertions()) { - v->m_ast_vector.push_back(e); - } - return of_ast_vector(v); + for (unsigned i = 0; i < num_sorts; ++i) + insert_sort(m, ctx, to_symbol(_sort_names[i]), to_sort(_sorts[i])); + + return Z3_parser_context_parse_stream(c, ctx, true, is); Z3_CATCH_RETURN(nullptr); } @@ -155,11 +231,13 @@ extern "C" { Z3_TRY; LOG_Z3_eval_smtlib2_string(c, str); if (!mk_c(c)->cmd()) { - mk_c(c)->cmd() = alloc(cmd_context, false, &(mk_c(c)->m())); - install_dl_cmds(*mk_c(c)->cmd()); - install_opt_cmds(*mk_c(c)->cmd()); - install_smt2_extra_cmds(*mk_c(c)->cmd()); - mk_c(c)->cmd()->set_solver_factory(mk_smt_strategic_solver_factory()); + auto* ctx = alloc(cmd_context, false, &(mk_c(c)->m())); + mk_c(c)->cmd() = ctx; + install_dl_cmds(*ctx); + install_opt_cmds(*ctx); + install_smt2_extra_cmds(*ctx); + ctx->register_plist(); + ctx->set_solver_factory(mk_smt_strategic_solver_factory()); } scoped_ptr& ctx = mk_c(c)->cmd(); std::string s(str); @@ -180,4 +258,6 @@ extern "C" { RETURN_Z3(mk_c(c)->mk_external_string(ous.str())); Z3_CATCH_RETURN(mk_c(c)->mk_external_string(ous.str())); } + + }; diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 8e5ebcf2a..bf8a52d51 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -256,7 +256,10 @@ extern "C" { } void solver_from_stream(Z3_context c, Z3_solver s, std::istream& is) { - scoped_ptr ctx = alloc(cmd_context, false, &(mk_c(c)->m())); + auto& solver = *to_solver(s); + if (!solver.m_cmd_context) + solver.m_cmd_context = alloc(cmd_context, false, &(mk_c(c)->m())); + auto& ctx = solver.m_cmd_context; ctx->set_ignore_check(true); std::stringstream errstrm; ctx->set_regular_stream(errstrm); @@ -272,6 +275,7 @@ extern "C" { init_solver(c, s); for (expr* e : ctx->tracked_assertions()) to_solver(s)->assert_expr(e); + ctx->reset_tracked_assertions(); to_solver_ref(s)->set_model_converter(ctx->get_model_converter()); } @@ -387,9 +391,11 @@ extern "C" { bool new_model = params.get_bool("model", true); if (old_model != new_model) to_solver_ref(s)->set_produce_models(new_model); - param_descrs r; - to_solver_ref(s)->collect_param_descrs(r); - context_params::collect_solver_param_descrs(r); + param_descrs& r = to_solver(s)->m_param_descrs; + if(r.size () == 0) { + to_solver_ref(s)->collect_param_descrs(r); + context_params::collect_solver_param_descrs(r); + } params.validate(r); to_solver_ref(s)->updt_params(params); } diff --git a/src/api/api_solver.h b/src/api/api_solver.h index 5e01d5349..5214d274c 100644 --- a/src/api/api_solver.h +++ b/src/api/api_solver.h @@ -42,8 +42,10 @@ struct Z3_solver_ref : public api::object { scoped_ptr m_solver_factory; ref m_solver; params_ref m_params; + param_descrs m_param_descrs; symbol m_logic; scoped_ptr m_pp; + scoped_ptr m_cmd_context; mutex m_mux; event_handler* m_eh; diff --git a/src/api/api_util.h b/src/api/api_util.h index 80fea1ac3..80f4f5ec7 100644 --- a/src/api/api_util.h +++ b/src/api/api_util.h @@ -19,6 +19,7 @@ Revision History: #include "util/params.h" #include "util/lbool.h" +#include "util/mutex.h" #include "ast/ast.h" #define Z3_TRY try { @@ -34,7 +35,7 @@ namespace api { // Generic wrapper for ref-count objects exposed by the API class object { - unsigned m_ref_count; + atomic m_ref_count; unsigned m_id; context& m_context; public: @@ -87,7 +88,6 @@ inline lbool to_lbool(Z3_lbool b) { return static_cast(b); } struct Z3_params_ref : public api::object { params_ref m_params; Z3_params_ref(api::context& c): api::object(c) {} - ~Z3_params_ref() override {} }; inline Z3_params_ref * to_params(Z3_params p) { return reinterpret_cast(p); } @@ -97,7 +97,6 @@ inline params_ref& to_param_ref(Z3_params p) { return p == nullptr ? const_cast< struct Z3_param_descrs_ref : public api::object { param_descrs m_descrs; Z3_param_descrs_ref(api::context& c): api::object(c) {} - ~Z3_param_descrs_ref() override {} }; inline Z3_param_descrs_ref * to_param_descrs(Z3_param_descrs p) { return reinterpret_cast(p); } diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index fc0601fe5..5f630d814 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -57,6 +57,8 @@ namespace z3 { class param_descrs; class ast; class sort; + class constructors; + class constructor_list; class func_decl; class expr; class solver; @@ -313,6 +315,34 @@ namespace z3 { */ func_decl tuple_sort(char const * name, unsigned n, char const * const * names, sort const* sorts, func_decl_vector & projs); + + /** + \brief Create a recursive datatype over a single sort. + \c name is the name of the recursive datatype + \c n - the numer of constructors of the datatype + \c cs - the \c n constructors used to define the datatype + + References to the datatype can be created using \ref datatype_sort. + */ + sort datatype(symbol const& name, constructors const& cs); + + /** + \brief Create a set of mutually recursive datatypes. + \c n - number of recursive datatypes + \c names - array of names of length n + \c cons - array of constructor lists of length n + */ + sort_vector datatypes(unsigned n, symbol const* names, + constructor_list *const* cons); + + + /** + \brief a reference to a recursively defined datatype. + Expect that it gets defined as a \ref datatype. + */ + sort datatype_sort(symbol const& name); + + /** \brief create an uninterpreted sort with the name given by the string or symbol. */ @@ -2894,7 +2924,7 @@ namespace z3 { if (n == 0) return ctx().bool_val(true); else if (n == 1) - return operator[](0); + return operator[](0u); else { array args(n); for (unsigned i = 0; i < n; i++) @@ -3330,6 +3360,98 @@ namespace z3 { return func_decl(*this, tuple); } + class constructor_list { + context& ctx; + Z3_constructor_list clist; + public: + constructor_list(constructors const& cs); + ~constructor_list() { Z3_del_constructor_list(ctx, clist); } + operator Z3_constructor_list() const { return clist; } + }; + + class constructors { + friend class constructor_list; + context& ctx; + std::vector cons; + std::vector num_fields; + public: + constructors(context& ctx): ctx(ctx) {} + + ~constructors() { + for (auto con : cons) + Z3_del_constructor(ctx, con); + } + + void add(symbol const& name, symbol const& rec, unsigned n, symbol const* names, sort const* fields) { + array sort_refs(n); + array sorts(n); + array _names(n); + for (unsigned i = 0; i < n; ++i) sorts[i] = fields[i], _names[i] = names[i]; + cons.push_back(Z3_mk_constructor(ctx, name, rec, n, _names.ptr(), sorts.ptr(), sort_refs.ptr())); + num_fields.push_back(n); + } + + Z3_constructor operator[](unsigned i) const { return cons[i]; } + + unsigned size() const { return (unsigned)cons.size(); } + + void query(unsigned i, func_decl& constructor, func_decl& test, func_decl_vector& accs) { + Z3_func_decl _constructor; + Z3_func_decl _test; + array accessors(num_fields[i]); + accs.resize(0); + Z3_query_constructor(ctx, + cons[i], + num_fields[i], + &_constructor, + &_test, + accessors.ptr()); + constructor = func_decl(ctx, _constructor); + + test = func_decl(ctx, _test); + for (unsigned j = 0; j < num_fields[i]; ++j) + accs.push_back(func_decl(ctx, accessors[j])); + } + }; + + constructor_list::constructor_list(constructors const& cs): ctx(cs.ctx) { + array cons(cs.size()); + for (unsigned i = 0; i < cs.size(); ++i) + cons[i] = cs[i]; + clist = Z3_mk_constructor_list(ctx, cs.size(), cons.ptr()); + } + + inline sort context::datatype(symbol const& name, constructors const& cs) { + array _cs(cs.size()); + for (unsigned i = 0; i < cs.size(); ++i) _cs[i] = cs[i]; + Z3_sort s = Z3_mk_datatype(*this, name, cs.size(), _cs.ptr()); + check_error(); + return sort(*this, s); + } + + inline sort_vector context::datatypes( + unsigned n, symbol const* names, + constructor_list *const* cons) { + sort_vector result(*this); + array _names(n); + array _sorts(n); + array _cons(n); + for (unsigned i = 0; i < n; ++i) + _names[i] = names[i], _cons[i] = *cons[i]; + Z3_mk_datatypes(*this, n, _names.ptr(), _sorts.ptr(), _cons.ptr()); + for (unsigned i = 0; i < n; ++i) + result.push_back(sort(*this, _sorts[i])); + return result; + } + + + inline sort context::datatype_sort(symbol const& name) { + Z3_sort s = Z3_mk_datatype_sort(*this, name); + check_error(); + return sort(*this, s); + } + + inline sort context::uninterpreted_sort(char const* name) { Z3_symbol _name = Z3_mk_string_symbol(*this, name); return to_sort(*this, Z3_mk_uninterpreted_sort(*this, _name)); diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs index c71289d4d..764beb470 100644 --- a/src/api/dotnet/Context.cs +++ b/src/api/dotnet/Context.cs @@ -4620,16 +4620,16 @@ namespace Microsoft.Z3 /// /// /// Produces a term that represents the conversion of the floating-point term t into a - /// bit-vector term of size sz in 2's complement format (signed when signed==true). If necessary, + /// bit-vector term of size sz in 2's complement format (signed when sign==true). If necessary, /// the result will be rounded according to rounding mode rm. /// /// RoundingMode term. /// FloatingPoint term /// Size of the resulting bit-vector. - /// Indicates whether the result is a signed or unsigned bit-vector. - public BitVecExpr MkFPToBV(FPRMExpr rm, FPExpr t, uint sz, bool signed) + /// Indicates whether the result is a signed or unsigned bit-vector. + public BitVecExpr MkFPToBV(FPRMExpr rm, FPExpr t, uint sz, bool sign) { - if (signed) + if (sign) return new BitVecExpr(this, Native.Z3_mk_fpa_to_sbv(this.nCtx, rm.NativeObject, t.NativeObject, sz)); else return new BitVecExpr(this, Native.Z3_mk_fpa_to_ubv(this.nCtx, rm.NativeObject, t.NativeObject, sz)); diff --git a/src/api/dotnet/Expr.cs b/src/api/dotnet/Expr.cs index f735401d8..7dabc49cd 100644 --- a/src/api/dotnet/Expr.cs +++ b/src/api/dotnet/Expr.cs @@ -168,6 +168,16 @@ namespace Microsoft.Z3 { return (Expr)base.Translate(ctx); } + + /// + /// Create a duplicate of expression. + /// This feature is to allow extending the life-time of expressions that were passed down as arguments + /// by the user propagator callbacks. By default the life-time of arguments to callbacks is within the + /// callback only. + /// + public Expr Dup() { + return Expr.Create(Context, NativeObject); + } /// /// Returns a string representation of the expression. diff --git a/src/api/dotnet/NativeContext.cs b/src/api/dotnet/NativeContext.cs index 93180e583..afae66e46 100644 --- a/src/api/dotnet/NativeContext.cs +++ b/src/api/dotnet/NativeContext.cs @@ -100,6 +100,17 @@ namespace Microsoft.Z3 return Native.Z3_mk_mul(nCtx, (uint)(ts?.Length ?? 0), ts); } + /// + /// Create an expression representing t[0] - t[1] - .... + /// + public Z3_ast MkSub(params Z3_ast[] t) + { + Debug.Assert(t != null); + Debug.Assert(t.All(a => a != IntPtr.Zero)); + var ts = t.ToArray(); + return Native.Z3_mk_sub(nCtx, (uint)(ts?.Length ?? 0), ts); + } + /// /// Create an expression representing t1 / t2. /// diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index ce9680a7a..3e9344556 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -41,6 +41,9 @@ namespace Microsoft.Z3 { /// /// Delegate type for fixed callback + /// Note that the life-time of the term and value only applies within the scope of the callback. + /// That means the term and value cannot be stored in an array, dictionary or similar and accessed after the callback has returned. + /// Use the functionality Dup on expressions to create a duplicate copy that extends the lifetime. /// public delegate void FixedEh(Expr term, Expr value); @@ -64,8 +67,7 @@ namespace Microsoft.Z3 // access managed objects through a static array. // thread safety is ignored for now. - static List propagators = new List(); - int id; + GCHandle gch; Solver solver; Context ctx; Z3_solver_callback callback = IntPtr.Zero; @@ -107,27 +109,27 @@ namespace Microsoft.Z3 static void _push(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.Push(), cb); } static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.Pop(num_scopes), cb); } static voidp _fresh(voidp _ctx, Z3_context new_context) { - var prop = propagators[_ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(_ctx).Target; var ctx = new Context(new_context); var prop1 = prop.Fresh(prop.ctx); - return new IntPtr(prop1.id); + return GCHandle.ToIntPtr(prop1.gch); } static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var term = Expr.Create(prop.ctx, _term); using var value = Expr.Create(prop.ctx, _value); prop.Callback(() => prop.fixed_eh(term, value), cb); @@ -135,13 +137,13 @@ namespace Microsoft.Z3 static void _final(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.final_eh(), cb); } static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.eq_eh(s, t), cb); @@ -149,7 +151,7 @@ namespace Microsoft.Z3 static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.diseq_eh(s, t), cb); @@ -157,14 +159,14 @@ namespace Microsoft.Z3 static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var t = Expr.Create(prop.ctx, a); prop.Callback(() => prop.created_eh(t), cb); } static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; var t = Expr.Create(prop.ctx, a); var u = t; prop.callback = cb; @@ -179,14 +181,13 @@ namespace Microsoft.Z3 /// public UserPropagator(Solver s) { - id = propagators.Count; - propagators.Add(this); + gch = GCHandle.Alloc(this); solver = s; ctx = solver.Context; push_eh = _push; pop_eh = _pop; fresh_eh = _fresh; - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), push_eh, pop_eh, fresh_eh); + Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, GCHandle.ToIntPtr(gch), push_eh, pop_eh, fresh_eh); } /// @@ -194,8 +195,7 @@ namespace Microsoft.Z3 /// public UserPropagator(Context _ctx) { - id = propagators.Count; - propagators.Add(this); + gch = GCHandle.Alloc(this); solver = null; ctx = _ctx; } @@ -205,7 +205,7 @@ namespace Microsoft.Z3 /// ~UserPropagator() { - propagators[id] = null; + gch.Free(); if (solver == null) ctx.Dispose(); } diff --git a/src/api/java/Context.java b/src/api/java/Context.java index 7b5f8a936..4582439ec 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -1717,8 +1717,8 @@ public class Context implements AutoCloseable { * {@code [domain -> range]}, and {@code i} must have the sort * {@code domain}. The sort of the result is {@code range}. * - * @see #mkArraySort(Sort[], Sort) - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ public Expr mkSelect(Expr> a, Expr i) { @@ -1739,8 +1739,8 @@ public class Context implements AutoCloseable { * {@code [domains -> range]}, and {@code args} must have the sorts * {@code domains}. The sort of the result is {@code range}. * - * @see #mkArraySort(Sort[], Sort) - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ public Expr mkSelect(Expr> a, Expr[] args) { @@ -1763,8 +1763,8 @@ public class Context implements AutoCloseable { * {@code select}) on all indices except for {@code i}, where it * maps to {@code v} (and the {@code select} of {@code a} * with respect to {@code i} may be a different value). - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) **/ public ArrayExpr mkStore(Expr> a, Expr i, Expr v) @@ -1788,8 +1788,8 @@ public class Context implements AutoCloseable { * {@code select}) on all indices except for {@code args}, where it * maps to {@code v} (and the {@code select} of {@code a} * with respect to {@code args} may be a different value). - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) **/ public ArrayExpr mkStore(Expr> a, Expr[] args, Expr v) @@ -1806,8 +1806,8 @@ public class Context implements AutoCloseable { * Remarks: The resulting term is an array, such * that a {@code select} on an arbitrary index produces the value * {@code v}. - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) * **/ public ArrayExpr mkConstArray(D domain, Expr v) @@ -1826,9 +1826,9 @@ public class Context implements AutoCloseable { * {@code f} must have type {@code range_1 .. range_n -> range}. * {@code v} must have sort range. The sort of the result is * {@code [domain_i -> range]}. - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ @SafeVarargs @@ -2476,7 +2476,7 @@ public class Context implements AutoCloseable { * * @return A Term with value {@code num}/{@code den} * and sort Real - * @see #mkNumeral(String,Sort) + * @see #mkNumeral(String v, R ty) **/ public RatNum mkReal(int num, int den) { @@ -2612,7 +2612,7 @@ public class Context implements AutoCloseable { * 'names' of the bound variables, and {@code body} is the body * of the quantifier. Quantifiers are associated with weights indicating the * importance of using the quantifier during instantiation. - * Note that the bound variables are de-Bruijn indices created using {@link #mkBound}. + * Note that the bound variables are de-Bruijn indices created using {#mkBound}. * Z3 applies the convention that the last element in {@code names} and * {@code sorts} refers to the variable with index 0, the second to last element * of {@code names} and {@code sorts} refers to the variable @@ -2707,7 +2707,7 @@ public class Context implements AutoCloseable { * with the sorts of the bound variables, {@code names} is an array with the * 'names' of the bound variables, and {@code body} is the body of the * lambda. - * Note that the bound variables are de-Bruijn indices created using {@link #mkBound} + * Note that the bound variables are de-Bruijn indices created using {#mkBound} * Z3 applies the convention that the last element in {@code names} and * {@code sorts} refers to the variable with index 0, the second to last element * of {@code names} and {@code sorts} refers to the variable diff --git a/src/api/java/Model.java b/src/api/java/Model.java index 032745fc5..ffc4dd47f 100644 --- a/src/api/java/Model.java +++ b/src/api/java/Model.java @@ -242,7 +242,7 @@ public class Model extends Z3Object { * values. We say this finite set is the "universe" of the sort. * * @see #getNumSorts - * @see #getSortUniverse + * @see #getSortUniverse(R s) * * @throws Z3Exception **/ diff --git a/src/api/java/NativeStatic.txt b/src/api/java/NativeStatic.txt index 657050951..4693272d5 100644 --- a/src/api/java/NativeStatic.txt +++ b/src/api/java/NativeStatic.txt @@ -1,4 +1,4 @@ -// Automatically generated file + #include #include #include"z3.h" diff --git a/src/api/java/Quantifier.java b/src/api/java/Quantifier.java index 89ff61a3d..efeac9bb5 100644 --- a/src/api/java/Quantifier.java +++ b/src/api/java/Quantifier.java @@ -161,6 +161,12 @@ public class Quantifier extends BoolExpr /** * Create a quantified expression. * + * @param ctx Context to create the quantifier on. + * @param isForall Quantifier type. + * @param sorts Sorts of bound variables. + * @param names Names of bound variables + * @param body Body of quantifier + * @param weight Weight used to indicate priority for qunatifier instantiation * @param patterns Nullable patterns * @param noPatterns Nullable noPatterns * @param quantifierID Nullable quantifierID diff --git a/src/api/js/README.md b/src/api/js/README.md index 27ff5bdfe..42c29518e 100644 --- a/src/api/js/README.md +++ b/src/api/js/README.md @@ -9,7 +9,7 @@ The readme for the bindings themselves is located in [`PUBLISHED_README.md`](./P You'll need to have emscripten set up, along with all of its dependencies. The easiest way to do that is with [emsdk](https://github.com/emscripten-core/emsdk). -Then run `npm i` to install dependencies, `npm run build-ts` to build the TypeScript wrapper, and `npm run build-wasm` to build the wasm artifact. +Then run `npm i` to install dependencies, `npm run build:ts` to build the TypeScript wrapper, and `npm run build:wasm` to build the wasm artifact. ## Tests diff --git a/src/api/js/package-lock.json b/src/api/js/package-lock.json index c860eda9b..d736468a9 100644 --- a/src/api/js/package-lock.json +++ b/src/api/js/package-lock.json @@ -27,7 +27,7 @@ "typescript": "^4.5.4" }, "engines": { - "node": ">=16 <18" + "node": ">=16" } }, "node_modules/@ampproject/remapping": { diff --git a/src/api/js/package.json b/src/api/js/package.json index 53fd25b2a..7249b85a2 100644 --- a/src/api/js/package.json +++ b/src/api/js/package.json @@ -12,13 +12,13 @@ "homepage": "https://github.com/Z3Prover/z3/tree/master/src/api/js", "repository": "github:Z3Prover/z3", "engines": { - "node": ">=16 <18" + "node": ">=16" }, "browser": "build/browser.js", "main": "build/node.js", "types": "build/node.d.ts", "files": [ - "build/*.{js,d.ts,wasm}" + "build/**/*.{js,d.ts,wasm}" ], "scripts": { "build:ts": "run-s -l build:ts:generate build:ts:tsc", diff --git a/src/api/ml/z3.ml b/src/api/ml/z3.ml index 36753a4f9..2fa4acc65 100644 --- a/src/api/ml/z3.ml +++ b/src/api/ml/z3.ml @@ -59,6 +59,7 @@ let mk_context (settings:(string * string) list) = Z3native.del_config cfg; Z3native.set_ast_print_mode res (Z3enums.int_of_ast_print_mode PRINT_SMTLIB2_COMPLIANT); Z3native.set_internal_error_handler res; + Z3native.enable_concurrent_dec_ref res; res module Symbol = diff --git a/src/api/ml/z3.mli b/src/api/ml/z3.mli index 74320dd72..b7fa27b5e 100644 --- a/src/api/ml/z3.mli +++ b/src/api/ml/z3.mli @@ -49,6 +49,13 @@ type context val mk_context : (string * string) list -> context (** Interaction logging for Z3 + Interaction logs are used to record calls into the API into a text file. + The text file can be replayed using z3. It has to be the same version of z3 + to ensure that low level codes emitted in a log are compatible with the + version of z3 replaying the log. The file suffix ".log" is understood + by z3 as the format of the file being an interaction log. You can use the + optional comman-line parameter "-log" to force z3 to treat an input file + as an interaction log. Note that this is a global, static log and if multiple Context objects are created, it logs the interaction with all of them. *) module Log : @@ -927,10 +934,10 @@ end module FiniteDomain : sig (** Create a new finite domain sort. *) - val mk_sort : context -> Symbol.symbol -> int -> Sort.sort + val mk_sort : context -> Symbol.symbol -> int64 -> Sort.sort (** Create a new finite domain sort. *) - val mk_sort_s : context -> string -> int -> Sort.sort + val mk_sort_s : context -> string -> int64 -> Sort.sort (** Indicates whether the term is of an array sort. *) val is_finite_domain : Expr.expr -> bool @@ -939,7 +946,7 @@ sig val is_lt : Expr.expr -> bool (** The size of the finite domain sort. *) - val get_size : Sort.sort -> int + val get_size : Sort.sort -> int64 end @@ -2078,7 +2085,7 @@ sig val mk_numeral_i : context -> int -> Sort.sort -> Expr.expr (** Create a numeral of FloatingPoint sort from a sign bit and two integers. *) - val mk_numeral_i_u : context -> bool -> int -> int -> Sort.sort -> Expr.expr + val mk_numeral_i_u : context -> bool -> int64 -> int64 -> Sort.sort -> Expr.expr (** Create a numeral of FloatingPoint sort from a string *) val mk_numeral_s : context -> string -> Sort.sort -> Expr.expr @@ -2303,7 +2310,7 @@ sig val get_numeral_exponent_string : context -> Expr.expr -> bool -> string (** Return the exponent value of a floating-point numeral as a signed integer *) - val get_numeral_exponent_int : context -> Expr.expr -> bool -> bool * int + val get_numeral_exponent_int : context -> Expr.expr -> bool -> bool * int64 (** Return the exponent of a floating-point numeral as a bit-vector expression. Remark: NaN's do not have a bit-vector exponent, so they are invalid arguments. *) @@ -2320,7 +2327,7 @@ sig Remark: This function extracts the significand bits, without the hidden bit or normalization. Throws an exception if the significand does not fit into an int. *) - val get_numeral_significand_uint : context -> Expr.expr -> bool * int + val get_numeral_significand_uint : context -> Expr.expr -> bool * int64 (** Indicates whether a floating-point numeral is a NaN. *) val is_numeral_nan : context -> Expr.expr -> bool diff --git a/src/api/ml/z3native.ml.pre b/src/api/ml/z3native.ml.pre index 8f76aa950..93df8ad07 100644 --- a/src/api/ml/z3native.ml.pre +++ b/src/api/ml/z3native.ml.pre @@ -21,6 +21,7 @@ and solver_callback = ptr and goal = ptr and tactic = ptr and params = ptr +and parser_context = ptr and probe = ptr and stats = ptr and ast_vector = ptr diff --git a/src/api/ml/z3native_stubs.c.pre b/src/api/ml/z3native_stubs.c.pre index 0efaa110f..e9cfa443b 100644 --- a/src/api/ml/z3native_stubs.c.pre +++ b/src/api/ml/z3native_stubs.c.pre @@ -418,6 +418,7 @@ MK_PLUS_OBJ_NO_REF(constructor_list, 16) MK_PLUS_OBJ_NO_REF(rcf_num, 16) MK_PLUS_OBJ(params, 64) MK_PLUS_OBJ(param_descrs, 64) +MK_PLUS_OBJ(parser_context, 64) MK_PLUS_OBJ(model, 64) MK_PLUS_OBJ(func_interp, 32) MK_PLUS_OBJ(func_entry, 32) diff --git a/src/api/python/setup.py b/src/api/python/setup.py index 572b0a7a7..7d1f43629 100644 --- a/src/api/python/setup.py +++ b/src/api/python/setup.py @@ -129,6 +129,15 @@ def _configure_z3(): for key, val in cmake_options.items(): if type(val) is bool: cmake_options[key] = str(val).upper() + + # Allow command-line arguments to add and override Z3_ options + for i in range(len(sys.argv) - 1): + key = sys.argv[i] + if key.starts_with("Z3_"): + val = sys.argv[i + 1].upper() + if val == "TRUE" or val == "FALSE": + cmake_options[key] = val + cmake_args = [ '-D' + key + '=' + value for key,value in cmake_options.items() ] args = [ 'cmake', *cmake_args, SRC_DIR ] if subprocess.call(args, env=build_env, cwd=BUILD_DIR) != 0: diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 7dbef9f6e..11dcedbe8 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -5345,6 +5345,10 @@ class DatatypeRef(ExprRef): """Return the datatype sort of the datatype expression `self`.""" return DatatypeSortRef(Z3_get_sort(self.ctx_ref(), self.as_ast()), self.ctx) +def DatatypeSort(name, ctx = None): + """Create a reference to a sort that was declared, or will be declared, as a recursive datatype""" + ctx = _get_ctx(ctx) + return DatatypeSortRef(Z3_mk_datatype_sort(ctx.ref(), to_symbol(name, ctx)), ctx) def TupleSort(name, sorts, ctx=None): """Create a named tuple sort base on a set of underlying sorts @@ -9192,6 +9196,25 @@ def _dict2darray(decls, ctx): i = i + 1 return sz, _names, _decls +class ParserContext: + def __init__(self, ctx= None): + self.ctx = _get_ctx(ctx) + self.pctx = Z3_mk_parser_context(self.ctx.ref()) + Z3_parser_context_inc_ref(self.ctx.ref(), self.pctx) + + def __del__(self): + if self.ctx.ref() is not None and self.pctx is not None and Z3_parser_context_dec_ref is not None: + Z3_parser_context_dec_ref(self.ctx.ref(), self.pctx) + self.pctx = None + + def add_sort(self, sort): + Z3_parser_context_add_sort(self.ctx.ref(), self.pctx, sort.as_ast()) + + def add_decl(self, decl): + Z3_parser_context_add_decl(self.ctx.ref(), self.pctx, decl.as_ast()) + + def from_string(self, s): + return AstVector(Z3_parser_context_from_string(self.ctx.ref(), self.pctx, s), self.ctx) def parse_smt2_string(s, sorts={}, decls={}, ctx=None): """Parse a string in SMT 2.0 format using the given sorts and decls. @@ -11334,11 +11357,20 @@ def user_prop_pop(ctx, cb, num_scopes): prop.cb = cb prop.pop(num_scopes) +def to_ContextObj(ptr,): + ctx = ContextObj(ptr) + super(ctypes.c_void_p, ctx).__init__(ptr) + return ctx -def user_prop_fresh(id, ctx): + +def user_prop_fresh(ctx, new_ctx): _prop_closures.set_threaded() - prop = _prop_closures.get(id) - new_prop = prop.fresh() + prop = _prop_closures.get(ctx) + nctx = Context() + new_ctx = to_ContextObj(new_ctx) + nctx.ctx = new_ctx + nctx.eh = Z3_set_error_handler(new_ctx, z3_error_handler) + new_prop = prop.fresh(nctx) _prop_closures.set(new_prop.id, new_prop) return ctypes.c_void_p(new_prop.id) @@ -11401,6 +11433,7 @@ class UserPropagateBase: ensure_prop_closures() self.solver = s self._ctx = None + self.fresh_ctx = None self.cb = None self.id = _prop_closures.insert(self) self.fixed = None @@ -11408,12 +11441,7 @@ 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) + self.fresh_ctx = ctx if s: Z3_solver_propagate_init(self.ctx_ref(), s.solver, @@ -11427,8 +11455,8 @@ class UserPropagateBase: self._ctx.ctx = None def ctx(self): - if self._ctx: - return self._ctx + if self.fresh_ctx: + return self.fresh_ctx else: return self.solver.ctx @@ -11438,25 +11466,29 @@ class UserPropagateBase: def add_fixed(self, fixed): assert not self.fixed assert not self._ctx - Z3_solver_propagate_fixed(self.ctx_ref(), self.solver.solver, _user_prop_fixed) + if self.solver: + Z3_solver_propagate_fixed(self.ctx_ref(), self.solver.solver, _user_prop_fixed) self.fixed = fixed def add_final(self, final): assert not self.final assert not self._ctx - Z3_solver_propagate_final(self.ctx_ref(), self.solver.solver, _user_prop_final) + if self.solver: + Z3_solver_propagate_final(self.ctx_ref(), self.solver.solver, _user_prop_final) self.final = final def add_eq(self, eq): assert not self.eq assert not self._ctx - Z3_solver_propagate_eq(self.ctx_ref(), self.solver.solver, _user_prop_eq) + if self.solver: + Z3_solver_propagate_eq(self.ctx_ref(), self.solver.solver, _user_prop_eq) self.eq = eq def add_diseq(self, diseq): assert not self.diseq assert not self._ctx - Z3_solver_propagate_diseq(self.ctx_ref(), self.solver.solver, _user_prop_diseq) + if self.solver: + Z3_solver_propagate_diseq(self.ctx_ref(), self.solver.solver, _user_prop_diseq) self.diseq = diseq def push(self): @@ -11465,7 +11497,7 @@ class UserPropagateBase: def pop(self, num_scopes): raise Z3Exception("pop needs to be overwritten") - def fresh(self): + def fresh(self, new_ctx): raise Z3Exception("fresh needs to be overwritten") def add(self, e): diff --git a/src/api/python/z3/z3types.py b/src/api/python/z3/z3types.py index 6c93c0bee..500e3606e 100644 --- a/src/api/python/z3/z3types.py +++ b/src/api/python/z3/z3types.py @@ -216,6 +216,13 @@ class ParamDescrs(ctypes.c_void_p): def from_param(obj): return obj +class ParserContextObj(ctypes.c_void_p): + def __init__(self, pc): + self._as_parameter_ = pc + + def from_param(obj): + return obj + class FuncInterpObj(ctypes.c_void_p): def __init__(self, f): diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 8c60a09d1..2820205b9 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -20,6 +20,7 @@ DEFINE_TYPE(Z3_constructor); DEFINE_TYPE(Z3_constructor_list); DEFINE_TYPE(Z3_params); DEFINE_TYPE(Z3_param_descrs); +DEFINE_TYPE(Z3_parser_context); DEFINE_TYPE(Z3_goal); DEFINE_TYPE(Z3_tactic); DEFINE_TYPE(Z3_probe); @@ -58,6 +59,7 @@ DEFINE_TYPE(Z3_rcf_num); - \c Z3_constructor_list: list of constructors for a (recursive) datatype. - \c Z3_params: parameter set used to configure many components such as: simplifiers, tactics, solvers, etc. - \c Z3_param_descrs: provides a collection of parameter names, their types, default values and documentation strings. Solvers, tactics, and other objects accept different collection of parameters. + - \c Z3_parser_context: context for incrementally parsing strings. Declarations can be added incrementally to the parser state. - \c Z3_model: model for the constraints asserted into the logical context. - \c Z3_func_interp: interpretation of a function in a model. - \c Z3_func_entry: representation of the value of a \c Z3_func_interp at a particular point. @@ -1413,6 +1415,7 @@ typedef enum def_Type('CONSTRUCTOR_LIST', 'Z3_constructor_list', 'ConstructorList') def_Type('SOLVER', 'Z3_solver', 'SolverObj') def_Type('SOLVER_CALLBACK', 'Z3_solver_callback', 'SolverCallbackObj') + def_Type('PARSER_CONTEXT', 'Z3_parser_context', 'ParserContextObj') def_Type('GOAL', 'Z3_goal', 'GoalObj') def_Type('TACTIC', 'Z3_tactic', 'TacticObj') def_Type('PARAMS', 'Z3_params', 'Params') @@ -1702,6 +1705,16 @@ extern "C" { void Z3_API Z3_interrupt(Z3_context c); + /** + \brief use concurrency control for dec-ref. + Reference counting decrements are allowed in separate threads from the context. + If this setting is not invoked, reference counting decrements are not going to be thread safe. + + def_API('Z3_enable_concurrent_dec_ref', VOID, (_in(CONTEXT),)) + */ + void Z3_API Z3_enable_concurrent_dec_ref(Z3_context c); + + /**@}*/ /** @name Parameters */ @@ -5827,6 +5840,55 @@ extern "C" { Z3_string Z3_API Z3_eval_smtlib2_string(Z3_context, Z3_string str); + + /** + \brief Create a parser context. + + A parser context maintains state between calls to \c Z3_parser_context_parse_string + where the caller can pass in a set of SMTLIB2 commands. + It maintains all the declarations from previous calls together with + of sorts and function declarations (including 0-ary) that are added directly to the context. + + def_API('Z3_mk_parser_context', PARSER_CONTEXT, (_in(CONTEXT),)) + */ + Z3_parser_context Z3_API Z3_mk_parser_context(Z3_context c); + + /** + \brief Increment the reference counter of the given \c Z3_parser_context object. + + def_API('Z3_parser_context_inc_ref', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT))) + */ + void Z3_API Z3_parser_context_inc_ref(Z3_context c, Z3_parser_context pc); + + /** + \brief Decrement the reference counter of the given \c Z3_parser_context object. + + def_API('Z3_parser_context_dec_ref', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT))) + */ + void Z3_API Z3_parser_context_dec_ref(Z3_context c, Z3_parser_context pc); + + /** + \brief Add a sort declaration. + + def_API('Z3_parser_context_add_sort', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(SORT))) + */ + void Z3_API Z3_parser_context_add_sort(Z3_context c, Z3_parser_context pc, Z3_sort s); + + /** + \brief Add a function declaration. + + def_API('Z3_parser_context_add_decl', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(FUNC_DECL))) + */ + void Z3_API Z3_parser_context_add_decl(Z3_context c, Z3_parser_context pc, Z3_func_decl f); + + /** + \brief Parse a string of SMTLIB2 commands. Return assertions. + + def_API('Z3_parser_context_from_string', AST_VECTOR, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(STRING))) + */ + Z3_ast_vector Z3_API Z3_parser_context_from_string(Z3_context c, Z3_parser_context pc, Z3_string s); + + /**@}*/ /** @name Error Handling */ @@ -6811,7 +6873,7 @@ extern "C" { /** \brief register a callback when a new expression with a registered function is used by the solver - The registered function appears at the top level and is created using \ref Z3_propagate_solver_declare. + The registered function appears at the top level and is created using \ref Z3_solver_propagate_declare. def_API('Z3_solver_propagate_created', VOID, (_in(CONTEXT), _in(SOLVER), _fnptr(Z3_created_eh))) */ @@ -6837,7 +6899,7 @@ extern "C" { /** Create uninterpreted function declaration for the user propagator. When expressions using the function are created by the solver invoke a callback - to \ref \Z3_solver_progate_created with arguments + to \ref \Z3_solver_propagate_created with arguments 1. context and callback solve 2. declared_expr: expression using function that was used as the top-level symbol 3. declared_id: a unique identifier (unique within the current scope) to track the expression. diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 5c48f31d0..473bd82b5 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -471,26 +471,31 @@ bool compare_nodes(ast const * n1, ast const * n2) { return to_var(n1)->get_idx() == to_var(n2)->get_idx() && to_var(n1)->get_sort() == to_var(n2)->get_sort(); - case AST_QUANTIFIER: + case AST_QUANTIFIER: { + quantifier const* q1 = to_quantifier(n1); + quantifier const* q2 = to_quantifier(n2); return - to_quantifier(n1)->get_kind() == to_quantifier(n2)->get_kind() && - to_quantifier(n1)->get_num_decls() == to_quantifier(n2)->get_num_decls() && - compare_arrays(to_quantifier(n1)->get_decl_sorts(), - to_quantifier(n2)->get_decl_sorts(), - to_quantifier(n1)->get_num_decls()) && - compare_arrays(to_quantifier(n1)->get_decl_names(), - to_quantifier(n2)->get_decl_names(), - to_quantifier(n1)->get_num_decls()) && - to_quantifier(n1)->get_expr() == to_quantifier(n2)->get_expr() && - to_quantifier(n1)->get_weight() == to_quantifier(n2)->get_weight() && - to_quantifier(n1)->get_num_patterns() == to_quantifier(n2)->get_num_patterns() && - compare_arrays(to_quantifier(n1)->get_patterns(), - to_quantifier(n2)->get_patterns(), - to_quantifier(n1)->get_num_patterns()) && - to_quantifier(n1)->get_num_no_patterns() == to_quantifier(n2)->get_num_no_patterns() && - compare_arrays(to_quantifier(n1)->get_no_patterns(), - to_quantifier(n2)->get_no_patterns(), - to_quantifier(n1)->get_num_no_patterns()); + q1->get_kind() == q2->get_kind() && + q1->get_num_decls() == q2->get_num_decls() && + compare_arrays(q1->get_decl_sorts(), + q2->get_decl_sorts(), + q1->get_num_decls()) && + compare_arrays(q1->get_decl_names(), + q2->get_decl_names(), + q1->get_num_decls()) && + q1->get_expr() == q2->get_expr() && + q1->get_weight() == q2->get_weight() && + q1->get_num_patterns() == q2->get_num_patterns() && + ((q1->get_qid().is_numerical() && q2->get_qid().is_numerical()) || + (q1->get_qid() == q2->get_qid())) && + compare_arrays(q1->get_patterns(), + q2->get_patterns(), + q1->get_num_patterns()) && + q1->get_num_no_patterns() == q2->get_num_no_patterns() && + compare_arrays(q1->get_no_patterns(), + q2->get_no_patterns(), + q1->get_num_no_patterns()); + } default: UNREACHABLE(); } diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index ca2e1584d..93e10f094 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -67,6 +67,30 @@ namespace recfun { m_decl = m.mk_func_decl(s, arity, domain, range, info); } + def* def::copy(util& dst, ast_translation& tr) { + SASSERT(&dst.m() == &tr.to()); + sort_ref_vector domain(tr.to()); + sort_ref range(tr(m_range.get()), tr.to()); + for (auto* s : m_domain) + domain.push_back(tr(s)); + family_id fid = dst.get_family_id(); + bool is_generated = m_decl->get_parameter(0).get_int() != 0; + def* r = alloc(def, tr.to(), fid, m_name, domain.size(), domain.data(), range, is_generated); + r->m_rhs = tr(m_rhs.get()); + for (auto* v : m_vars) + r->m_vars.push_back(tr(v)); + for (auto const& c1 : m_cases) { + r->m_cases.push_back(case_def(tr.to())); + auto& c2 = r->m_cases.back(); + c2.m_pred = tr(c1.m_pred.get()); + c2.m_guards = tr(c1.m_guards); + c2.m_rhs = tr(c1.m_rhs.get()); + c2.m_def = r; + c2.m_immediate = c1.m_immediate; + } + return r; + } + bool def::contains_def(util& u, expr * e) { struct def_find_p : public i_expr_pred { util& u; @@ -415,6 +439,19 @@ namespace recfun { return promise_def(&u(), d); } + void plugin::inherit(decl_plugin* other, ast_translation& tr) { + for (auto [k, v] : static_cast(other)->m_defs) { + func_decl_ref f(tr(k), tr.to()); + if (m_defs.contains(f)) + continue; + def* d = v->copy(u(), tr); + m_defs.insert(f, d); + for (case_def & c : d->get_cases()) + m_case_defs.insert(c.get_decl(), &c); + + } + } + promise_def plugin::ensure_def(symbol const& name, unsigned n, sort *const * params, sort * range, bool is_generated) { def* d = u().decl_fun(name, n, params, range, is_generated); erase_def(d->get_decl()); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 70447ff67..bbc4e5810 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -21,6 +21,7 @@ Revision History: #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/ast_translation.h" #include "util/obj_hashtable.h" namespace recfun { @@ -62,6 +63,12 @@ namespace recfun { def * m_def; //is_const_char(a, n)) + return BR_FAILED; + result = m.mk_bool_val('0' <= n && n <= '9'); + return BR_DONE; +} + +br_status char_rewriter::mk_char_to_bv(expr* a, expr_ref& result) { + return BR_FAILED; +} + +br_status char_rewriter::mk_char_le(expr* a, expr* b, expr_ref& result) { + unsigned na = 0, nb = 0; + if (m_char->is_const_char(a, na)) { + if (na == 0) { + result = m.mk_true(); + return BR_DONE; + } + } + if (m_char->is_const_char(b, nb)) { + if (na != 0) { + result = m.mk_bool_val(na <= nb); + return BR_DONE; + } + if (nb == m_char->max_char()) { + result = m.mk_true(); + return BR_DONE; + } + } + return BR_FAILED; +} + br_status char_rewriter::mk_char_from_bv(expr* e, expr_ref& result) { bv_util bv(m); rational n; diff --git a/src/ast/rewriter/char_rewriter.h b/src/ast/rewriter/char_rewriter.h index f67695bd8..0ff833849 100644 --- a/src/ast/rewriter/char_rewriter.h +++ b/src/ast/rewriter/char_rewriter.h @@ -35,6 +35,12 @@ class char_rewriter { br_status mk_char_to_int(expr* e, expr_ref& result); + br_status mk_char_le(expr* a, expr* b, expr_ref& result); + + br_status mk_char_is_digit(expr* a, expr_ref& result); + + br_status mk_char_to_bv(expr* a, expr_ref& result); + public: char_rewriter(ast_manager& m); diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 95b9b8579..bf377df43 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -532,7 +532,12 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p case _OP_STRING_FROM_CHAR: { if (!(num_parameters == 1 && parameters[0].is_int())) m.raise_exception("character literal expects integer parameter"); - zstring zs(parameters[0].get_int()); + int i = parameters[0].get_int(); + if (i < 0) + m.raise_exception("character literal expects a non-negative integer parameter"); + if (i > (int)m_char_plugin->max_char()) + m.raise_exception("character literal is out of bounds"); + zstring zs(i); parameter p(zs); return m.mk_const_decl(m_stringc_sym, m_string,func_decl_info(m_family_id, OP_STRING_CONST, 1, &p)); } @@ -966,6 +971,22 @@ bool seq_util::str::is_len_sub(expr const* s, expr*& l, expr*& u, rational& k) c return false; } +bool seq_util::str::is_concat_of_units(expr* s) const { + ptr_vector todo; + todo.push_back(s); + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (is_empty(e) || is_unit(e)) + continue; + if (is_concat(e)) + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + else + return false; + } + return true; +} + bool seq_util::str::is_unit_string(expr const* s, expr_ref& c) const { zstring z; expr* ch = nullptr; diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 29d7b08ef..b0c3ab3c6 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -375,6 +375,7 @@ public: 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; + bool is_concat_of_units(expr* n) const; /* tests if s is a single character string(c) or a unit (c) diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index f3bd0ed57..dc8836138 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -219,9 +219,8 @@ ATOMIC_CMD(get_proof_graph_cmd, "get-proof-graph", "retrieve proof and print it pr = ctx.get_check_sat_result()->get_proof(); if (pr == 0) throw cmd_exception("proof is not available"); - if (ctx.well_sorted_check_enabled() && !is_well_sorted(ctx.m(), pr)) { + if (ctx.well_sorted_check_enabled() && !is_well_sorted(ctx.m(), pr)) throw cmd_exception("proof is not well sorted"); - } context_params& params = ctx.params(); const std::string& file = params.m_dot_proof_file; @@ -235,11 +234,11 @@ static void print_core(cmd_context& ctx) { ctx.regular_stream() << "("; bool first = true; for (expr* e : core) { - if (first) - first = false; - else - ctx.regular_stream() << " "; - ctx.regular_stream() << mk_ismt2_pp(e, ctx.m()); + if (first) + first = false; + else + ctx.regular_stream() << " "; + ctx.regular_stream() << mk_ismt2_pp(e, ctx.m()); } ctx.regular_stream() << ")" << std::endl; } @@ -260,9 +259,8 @@ ATOMIC_CMD(get_unsat_assumptions_cmd, "get-unsat-assumptions", "retrieve subset return; if (!ctx.produce_unsat_assumptions()) throw cmd_exception("unsat assumptions construction is not enabled, use command (set-option :produce-unsat-assumptions true)"); - if (!ctx.has_manager() || ctx.cs_state() != cmd_context::css_unsat) { + if (!ctx.has_manager() || ctx.cs_state() != cmd_context::css_unsat) throw cmd_exception("unsat assumptions is not available"); - } print_core(ctx); }); @@ -410,6 +408,15 @@ class set_option_cmd : public set_get_option_cmd { } } + static void check_no_assertions(cmd_context & ctx, symbol const & opt_name) { + if (ctx.has_assertions()) { + std::string msg = "error setting '"; + msg += opt_name.str(); + msg += "', option value cannot be modified after assertions have been added"; + throw cmd_exception(std::move(msg)); + } + } + void set_param(cmd_context & ctx, char const * value) { try { gparams::set(m_option, value); @@ -437,11 +444,11 @@ class set_option_cmd : public set_get_option_cmd { ctx.set_interactive_mode(to_bool(value)); } else if (m_option == m_produce_proofs) { - check_not_initialized(ctx, m_produce_proofs); + check_no_assertions(ctx, m_produce_proofs); ctx.set_produce_proofs(to_bool(value)); } else if (m_option == m_produce_unsat_cores) { - check_not_initialized(ctx, m_produce_unsat_cores); + check_no_assertions(ctx, m_produce_unsat_cores); ctx.set_produce_unsat_cores(to_bool(value)); } else if (m_option == m_produce_unsat_assumptions) { diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 2174c9e0b..32dd6aee5 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -538,22 +538,9 @@ public: cmd_context::cmd_context(bool main_ctx, ast_manager * m, symbol const & l): m_main_ctx(main_ctx), m_logic(l), - m_interactive_mode(false), - m_global_decls(false), m_print_success(m_params.m_smtlib2_compliant), - m_random_seed(0), - m_produce_unsat_cores(false), - m_produce_unsat_assumptions(false), - m_produce_assignments(false), - m_status(UNKNOWN), - m_numeral_as_real(false), - m_ignore_check(false), - m_exit_on_error(false), m_manager(m), m_own_manager(m == nullptr), - m_manager_initialized(false), - m_pmanager(nullptr), - m_sexpr_manager(nullptr), m_regular("stdout", std::cout), m_diagnostic("stderr", std::cerr) { SASSERT(m != 0 || !has_manager()); @@ -607,6 +594,7 @@ void cmd_context::global_params_updated() { m_params.updt_params(); if (m_params.m_smtlib2_compliant) m_print_success = true; + set_produce_proofs(m_params.m_proof); if (m_solver) { params_ref p; if (!m_params.m_auto_config) @@ -626,13 +614,14 @@ void cmd_context::set_produce_models(bool f) { void cmd_context::set_produce_unsat_cores(bool f) { // can only be set before initialization - SASSERT(!has_manager()); + SASSERT(!has_assertions()); m_params.m_unsat_core |= f; } void cmd_context::set_produce_proofs(bool f) { - // can only be set before initialization - SASSERT(!has_manager()); + SASSERT(!has_assertions() || m_params.m_proof == f); + if (has_manager()) + m().toggle_proof_mode(f ? PGM_ENABLED : PGM_DISABLED); m_params.m_proof = f; } @@ -835,15 +824,16 @@ bool cmd_context::set_logic(symbol const & s) { TRACE("cmd_context", tout << s << "\n";); if (has_logic()) throw cmd_exception("the logic has already been set"); - if (has_manager() && m_main_ctx) + if (has_assertions() && m_main_ctx) throw cmd_exception("logic must be set before initialization"); - if (!smt_logics::supported_logic(s)) { + if (!smt_logics::supported_logic(s)) return false; - } + m_logic = s; - if (smt_logics::logic_has_reals_only(s)) { + if (m_solver) + mk_solver(); + if (smt_logics::logic_has_reals_only(s)) m_numeral_as_real = true; - } return true; } @@ -1837,6 +1827,10 @@ void cmd_context::add_declared_functions(model& mdl) { } void cmd_context::display_sat_result(lbool r) { + if (has_manager() && m().has_trace_stream()) { + m().trace_stream().flush(); + } + switch (r) { case l_true: regular_stream() << "sat" << std::endl; @@ -2203,22 +2197,25 @@ expr_ref_vector cmd_context::tracked_assertions() { for (unsigned i = 0; i < assertions().size(); ++i) { expr* an = assertion_names()[i]; expr* asr = assertions()[i]; - if (an) { + if (an) result.push_back(m().mk_implies(an, asr)); - } - else { + else result.push_back(asr); - } } } else { - for (expr * e : assertions()) { + for (expr * e : assertions()) result.push_back(e); - } } return result; } +void cmd_context::reset_tracked_assertions() { + m_assertion_names.reset(); + for (expr* a : m_assertions) + m().dec_ref(a); + m_assertions.reset(); +} void cmd_context::display_assertions() { if (!m_interactive_mode) @@ -2254,9 +2251,8 @@ format_ns::format * cmd_context::pp(sort * s) const { } cmd_context::pp_env & cmd_context::get_pp_env() const { - if (m_pp_env.get() == nullptr) { + if (m_pp_env.get() == nullptr) const_cast(this)->m_pp_env = alloc(pp_env, *const_cast(this)); - } return *(m_pp_env.get()); } @@ -2314,9 +2310,8 @@ void cmd_context::display_smt2_benchmark(std::ostream & out, unsigned num, expr out << "(set-logic " << logic << ")" << std::endl; // collect uninterpreted function declarations decl_collector decls(m()); - for (unsigned i = 0; i < num; i++) { + for (unsigned i = 0; i < num; i++) decls.visit(assertions[i]); - } // TODO: display uninterpreted sort decls, and datatype decls. @@ -2342,9 +2337,8 @@ void cmd_context::slow_progress_sample() { svector labels; m_solver->get_labels(labels); regular_stream() << "(labels"; - for (symbol const& s : labels) { + for (symbol const& s : labels) regular_stream() << " " << s; - } regular_stream() << "))" << std::endl; } diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 60a6e930b..3dc49624b 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -194,21 +194,21 @@ public: protected: - ast_context_params m_params; + ast_context_params m_params; bool m_main_ctx; symbol m_logic; - bool m_interactive_mode; - bool m_global_decls; + bool m_interactive_mode = false; + bool m_global_decls = false; bool m_print_success; - unsigned m_random_seed; - bool m_produce_unsat_cores; - bool m_produce_unsat_assumptions; - bool m_produce_assignments; - status m_status; - bool m_numeral_as_real; - bool m_ignore_check; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. - bool m_exit_on_error; - bool m_allow_duplicate_declarations { false }; + unsigned m_random_seed = 0; + bool m_produce_unsat_cores = false; + bool m_produce_unsat_assumptions = false; + bool m_produce_assignments = false; + status m_status = UNKNOWN; + bool m_numeral_as_real = false; + bool m_ignore_check = false; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. + bool m_exit_on_error = false; + bool m_allow_duplicate_declarations = false; static std::ostringstream g_error_stream; @@ -216,9 +216,9 @@ protected: sref_vector m_mcs; ast_manager * m_manager; bool m_own_manager; - bool m_manager_initialized; - pdecl_manager * m_pmanager; - sexpr_manager * m_sexpr_manager; + bool m_manager_initialized = false; + pdecl_manager * m_pmanager = nullptr; + sexpr_manager * m_sexpr_manager = nullptr; check_logic m_check_logic; stream_ref m_regular; stream_ref m_diagnostic; @@ -362,6 +362,7 @@ public: bool produce_unsat_cores() const; bool well_sorted_check_enabled() const; bool validate_model_enabled() const; + bool has_assertions() const { return !m_assertions.empty(); } void set_produce_models(bool flag); void set_produce_unsat_cores(bool flag); void set_produce_proofs(bool flag); @@ -485,6 +486,7 @@ public: ptr_vector const& assertions() const { return m_assertions; } ptr_vector const& assertion_names() const { return m_assertion_names; } expr_ref_vector tracked_assertions(); + void reset_tracked_assertions(); /** \brief Hack: consume assertions if there are no scopes. diff --git a/src/cmd_context/extra_cmds/dbg_cmds.cpp b/src/cmd_context/extra_cmds/dbg_cmds.cpp index 5a628ce58..c6f0b479a 100644 --- a/src/cmd_context/extra_cmds/dbg_cmds.cpp +++ b/src/cmd_context/extra_cmds/dbg_cmds.cpp @@ -377,8 +377,8 @@ public: }; class get_interpolant_cmd : public cmd { - expr* m_a; - expr* m_b; + scoped_ptr m_a; + scoped_ptr m_b; public: get_interpolant_cmd():cmd("get-interpolant") {} char const * get_usage() const override { return " "; } @@ -388,17 +388,24 @@ public: return CPK_EXPR; } void set_next_arg(cmd_context& ctx, expr * arg) override { - if (m_a == nullptr) - m_a = arg; + ast_manager& m = ctx.m(); + if (!m.is_bool(arg)) + throw default_exception("argument to interpolation is not Boolean"); + if (!m_a) + m_a = alloc(expr_ref, arg, m); else - m_b = arg; + m_b = alloc(expr_ref, arg, m); } void prepare(cmd_context & ctx) override { m_a = nullptr; m_b = nullptr; } void execute(cmd_context & ctx) override { ast_manager& m = ctx.m(); qe::interpolator mbi(m); + if (!m_a || !m_b) + throw default_exception("interpolation requires two arguments"); + if (!m.is_bool(*m_a) || !m.is_bool(*m_b)) + throw default_exception("interpolation requires two Boolean arguments"); expr_ref itp(m); - mbi.pogo(ctx.get_solver_factory(), m_a, m_b, itp); + mbi.pogo(ctx.get_solver_factory(), *m_a, *m_b, itp); ctx.regular_stream() << itp << "\n"; } }; diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index fb9854c50..cca2d5e04 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1741,6 +1741,87 @@ namespace dd { return (*this) * rational::power_of_two(n); } + /** + * \brief substitute variable v by r. + * This base line implementation is simplistic and does not use operator caching. + */ + pdd pdd::subst_pdd(unsigned v, pdd const& r) const { + if (is_val()) + return *this; + if (m.m_var2level[var()] < m.m_var2level[v]) + return *this; + pdd l = lo().subst_pdd(v, r); + pdd h = hi().subst_pdd(v, r); + if (var() == v) + return r*h + l; + else if (l == lo() && h == hi()) + return *this; + else + return m.mk_var(var())*h + l; + } + + std::pair pdd::var_factors() const { + if (is_val()) + return { unsigned_vector(), *this }; + unsigned v = var(); + if (lo().is_val()) { + if (!lo().is_zero()) + return { unsigned_vector(), *this }; + auto [vars, p] = hi().var_factors(); + vars.push_back(v); + return {vars, p}; + } + auto [lo_vars, q] = lo().var_factors(); + if (lo_vars.empty()) + return { unsigned_vector(), *this }; + + unsigned_vector lo_and_hi; + auto merge = [&](unsigned_vector& lo_vars, unsigned_vector& hi_vars) { + unsigned ir = 0, jr = 0; + for (unsigned i = 0, j = 0; i < lo_vars.size() || j < hi_vars.size(); ) { + if (i == lo_vars.size()) + hi_vars[jr++] = hi_vars[j++]; + else if (j == hi_vars.size()) + lo_vars[ir++] = lo_vars[i++]; + else if (lo_vars[i] == hi_vars[j]) { + lo_and_hi.push_back(lo_vars[i]); + ++i; + ++j; + } + else if (m.m_var2level[lo_vars[i]] > m.m_var2level[hi_vars[j]]) + hi_vars[jr++] = hi_vars[j++]; + else + lo_vars[ir++] = lo_vars[i++]; + } + lo_vars.shrink(ir); + hi_vars.shrink(jr); + }; + + auto mul = [&](unsigned_vector const& vars, pdd p) { + for (auto v : vars) + p *= m.mk_var(v); + return p; + }; + + auto [hi_vars, p] = hi().var_factors(); + if (lo_vars.back() == v) { + lo_vars.pop_back(); + merge(lo_vars, hi_vars); + lo_and_hi.push_back(v); + return { lo_and_hi, mul(lo_vars, q) + mul(hi_vars, p) }; + } + if (hi_vars.empty()) + return { unsigned_vector(), *this }; + + merge(lo_vars, hi_vars); + hi_vars.push_back(v); + if (lo_and_hi.empty()) + return { unsigned_vector(), *this }; + else + return { lo_and_hi, mul(lo_vars, q) + mul(hi_vars, p) }; + } + + std::ostream& operator<<(std::ostream& out, pdd const& b) { return b.display(out); } void pdd_iterator::next() { diff --git a/src/math/dd/dd_pdd.h b/src/math/dd/dd_pdd.h index 689403f4f..73828e0f8 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -447,11 +447,22 @@ namespace dd { bool resolve(unsigned v, pdd const& other, pdd& result) { return m.resolve(v, *this, other, result); } pdd reduce(unsigned v, pdd const& other) const { return m.reduce(v, *this, other); } + /** + * \brief factor out variables + */ + std::pair var_factors() const; + pdd subst_val0(vector> const& s) const { return m.subst_val0(*this, s); } pdd subst_val(pdd const& s) const { return m.subst_val(*this, s); } + pdd subst_val(vector> const& s) const { return m.subst_val0(*this, s); } pdd subst_val(unsigned v, rational const& val) const { return m.subst_val(*this, v, val); } pdd subst_add(unsigned var, rational const& val) { return m.subst_add(*this, var, val); } + /** + * \brief substitute variable v by r. + */ + pdd subst_pdd(unsigned v, pdd const& r) const; + std::ostream& display(std::ostream& out) const { return m.display(out, *this); } bool operator==(pdd const& other) const { return root == other.root; } bool operator!=(pdd const& other) const { return root != other.root; } diff --git a/src/math/dd/pdd_interval.h b/src/math/dd/pdd_interval.h index 55d2c9b21..d8e2b8db1 100644 --- a/src/math/dd/pdd_interval.h +++ b/src/math/dd/pdd_interval.h @@ -27,7 +27,33 @@ typedef dep_intervals::with_deps_t w_dep; class pdd_interval { dep_intervals& m_dep_intervals; std::function m_var2interval; - + + // retrieve intervals after distributing multiplication over addition. + template + void get_interval_distributed(pdd const& p, scoped_dep_interval& i, scoped_dep_interval& ret) { + bool deps = wd == w_dep::with_deps; + if (p.is_val()) { + if (deps) + m_dep_intervals.mul(p.val(), i, ret); + else + m_dep_intervals.mul(p.val(), i, ret); + return; + } + scoped_dep_interval hi(m()), lo(m()), t(m()), a(m()); + get_interval_distributed(p.lo(), i, lo); + m_var2interval(p.var(), deps, a); + if (deps) { + m_dep_intervals.mul(a, i, t); + get_interval_distributed(p.hi(), t, hi); + m_dep_intervals.add(hi, lo, ret); + } + else { + m_dep_intervals.mul(a, i, t); + get_interval_distributed(p.hi(), t, hi); + m_dep_intervals.add(hi, lo, ret); + } + } + public: pdd_interval(dep_intervals& d): m_dep_intervals(d) {} @@ -57,5 +83,11 @@ public: } } + template + void get_interval_distributed(pdd const& p, scoped_dep_interval& ret) { + scoped_dep_interval i(m()); + m_dep_intervals.set_interval_for_scalar(i, rational::one()); + get_interval_distributed(p, i, ret); + } }; } diff --git a/src/math/grobner/grobner.cpp b/src/math/grobner/grobner.cpp index 264c2a08c..39a327891 100644 --- a/src/math/grobner/grobner.cpp +++ b/src/math/grobner/grobner.cpp @@ -132,7 +132,7 @@ void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * } } -void grobner::display_monomial(std::ostream & out, monomial const & m) const { +void grobner::display_monomial(std::ostream & out, monomial const & m, std::function& display_var) const { if (!m.m_coeff.is_one() || m.m_vars.empty()) { out << m.m_coeff; if (!m.m_vars.empty()) @@ -165,7 +165,7 @@ void grobner::display_monomial(std::ostream & out, monomial const & m) const { } } -void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const { +void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials, std::function& display_var) const { bool first = true; for (unsigned i = 0; i < num_monomials; i++) { monomial const * m = monomials[i]; @@ -173,26 +173,26 @@ void grobner::display_monomials(std::ostream & out, unsigned num_monomials, mono first = false; else out << " + "; - display_monomial(out, *m); + display_monomial(out, *m, display_var); } } -void grobner::display_equation(std::ostream & out, equation const & eq) const { - display_monomials(out, eq.m_monomials.size(), eq.m_monomials.data()); +void grobner::display_equation(std::ostream & out, equation const & eq, std::function& display_var) const { + display_monomials(out, eq.m_monomials.size(), eq.m_monomials.data(), display_var); out << " = 0\n"; } -void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header) const { - if (!v.empty()) { - out << header << "\n"; - for (equation const* eq : v) - display_equation(out, *eq); - } +void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header, std::function& display_var) const { + if (v.empty()) + return; + out << header << "\n"; + for (equation const* eq : v) + display_equation(out, *eq, display_var); } -void grobner::display(std::ostream & out) const { - display_equations(out, m_processed, "processed:"); - display_equations(out, m_to_process, "to process:"); +void grobner::display(std::ostream & out, std::function& display_var) const { + display_equations(out, m_processed, "processed:", display_var); + display_equations(out, m_to_process, "to process:", display_var); } void grobner::set_weight(expr * n, int weight) { @@ -528,7 +528,7 @@ bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vectorm_vars[i2]); TRACE("grobner", - tout << "monomail: "; display_monomial(tout, *m1); tout << " is a subset of "; + tout << "monomial: "; display_monomial(tout, *m1); tout << " is a subset of "; display_monomial(tout, *m2); tout << "\n"; tout << "rest: "; display_vars(tout, rest.size(), rest.data()); tout << "\n";); return true; @@ -552,7 +552,7 @@ bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vector& display_var) const; - void display_equations(std::ostream & out, equation_set const & v, char const * header) const; + + void display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_monomials(out, num_monomials, monomials, _fn); + } + + + void display_equations(std::ostream & out, equation_set const & v, char const * header, std::function& display_var) const; void del_equations(unsigned old_size); @@ -281,11 +288,26 @@ public: void pop_scope(unsigned num_scopes); - void display_equation(std::ostream & out, equation const & eq) const; + void display_equation(std::ostream & out, equation const & eq) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_equation(out, eq, _fn); + } - void display_monomial(std::ostream & out, monomial const & m) const; + void display_monomial(std::ostream & out, monomial const & m) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_monomial(out, m, _fn); + } + + void display_equation(std::ostream & out, equation const & eq, std::function& display_var) const; - void display(std::ostream & out) const; + void display_monomial(std::ostream & out, monomial const & m, std::function& display_var) const; + + void display(std::ostream & out) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display(out, _fn); + } + + void display(std::ostream & out, std::function& display_var) const; }; diff --git a/src/math/grobner/pdd_simplifier.cpp b/src/math/grobner/pdd_simplifier.cpp index fe6d45d30..e8b10ad80 100644 --- a/src/math/grobner/pdd_simplifier.cpp +++ b/src/math/grobner/pdd_simplifier.cpp @@ -223,6 +223,8 @@ namespace dd { for (unsigned i = 0; i < s.m_to_simplify.size(); ++i) { equation* e = s.m_to_simplify[i]; pdd p = e->poly(); + if (p.is_val()) + continue; if (!p.hi().is_val()) { continue; } diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index 11c34e180..aefe5d6dc 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -11,9 +11,9 @@ --*/ +#include "util/uint_set.h" #include "math/grobner/pdd_solver.h" #include "math/grobner/pdd_simplifier.h" -#include "util/uint_set.h" #include @@ -169,7 +169,7 @@ namespace dd { /* Use the given equation to simplify equations in set */ - void solver::simplify_using(equation_vector& set, equation const& eq) { + void solver::simplify_using(equation_vector& set, std::function& simplifier) { struct scoped_update { equation_vector& set; unsigned i, j, sz; @@ -191,7 +191,7 @@ namespace dd { equation& target = *set[sr.i]; bool changed_leading_term = false; bool simplified = true; - simplified = !done() && try_simplify_using(target, eq, changed_leading_term); + simplified = !done() && simplifier(target, changed_leading_term); if (simplified && is_trivial(target)) { retire(&target); @@ -200,7 +200,6 @@ namespace dd { // pushed to solved } else if (simplified && changed_leading_term) { - SASSERT(target.state() == processed); push_equation(to_simplify, target); if (!m_var2level.empty()) { m_levelp1 = std::max(m_var2level[target.poly().var()]+1, m_levelp1); @@ -210,6 +209,13 @@ namespace dd { sr.nextj(); } } + } + + void solver::simplify_using(equation_vector& set, equation const& eq) { + std::function simplifier = [&](equation& target, bool& changed_leading_term) { + return try_simplify_using(target, eq, changed_leading_term); + }; + simplify_using(set, simplifier); } /* @@ -342,6 +348,7 @@ namespace dd { for (equation* e : m_solved) dealloc(e); for (equation* e : m_to_simplify) dealloc(e); for (equation* e : m_processed) dealloc(e); + m_subst.reset(); m_solved.reset(); m_processed.reset(); m_to_simplify.reset(); @@ -352,18 +359,54 @@ namespace dd { } void solver::add(pdd const& p, u_dependency * dep) { - if (p.is_zero()) return; - equation * eq = alloc(equation, p, dep); - if (check_conflict(*eq)) { + if (p.is_zero()) + return; + equation * eq = alloc(equation, p, dep); + if (check_conflict(*eq)) return; - } push_equation(to_simplify, eq); - if (!m_var2level.empty()) { + if (!m_var2level.empty()) m_levelp1 = std::max(m_var2level[p.var()]+1, m_levelp1); - } update_stats_max_degree_and_size(*eq); - } + } + + void solver::add_subst(unsigned v, pdd const& p, u_dependency* dep) { + m_subst.push_back({v, p, dep}); + if (!m_var2level.empty()) + m_levelp1 = std::max(m_var2level[v]+1, std::max(m_var2level[p.var()]+1, m_levelp1)); + + std::function simplifier = [&](equation& dst, bool& changed_leading_term) { + auto r = dst.poly().subst_pdd(v, p); + if (r == dst.poly()) + return false; + if (is_too_complex(r)) { + m_too_complex = true; + return false; + } + changed_leading_term = m.different_leading_term(r, dst.poly()); + dst = r; + dst = m_dep_manager.mk_join(dst.dep(), dep); + update_stats_max_degree_and_size(dst); + return true; + }; + if (!done()) + simplify_using(m_processed, simplifier); + if (!done()) + simplify_using(m_to_simplify, simplifier); + if (!done()) + simplify_using(m_solved, simplifier); + } + + void solver::simplify(pdd& p, u_dependency*& d) { + for (auto const& [v, q, d2] : m_subst) { + pdd r = p.subst_pdd(v, q); + if (r != p) { + p = r; + d = m_dep_manager.mk_join(d, d2); + } + } + } bool solver::canceled() { return m_limit.is_canceled(); @@ -446,9 +489,24 @@ namespace dd { } std::ostream& solver::display(std::ostream& out) const { - out << "solved\n"; for (auto e : m_solved) display(out, *e); - out << "processed\n"; for (auto e : m_processed) display(out, *e); - out << "to_simplify\n"; for (auto e : m_to_simplify) display(out, *e); + if (!m_solved.empty()) { + out << "solved\n"; for (auto e : m_solved) display(out, *e); + } + if (!m_processed.empty()) { + out << "processed\n"; for (auto e : m_processed) display(out, *e); + } + if (!m_to_simplify.empty()) { + out << "to_simplify\n"; for (auto e : m_to_simplify) display(out, *e); + } + if (!m_subst.empty()) { + out << "subst\n"; + for (auto const& [v, p, d] : m_subst) { + out << "v" << v << " := " << p; + if (m_print_dep) + m_print_dep(d, out); + out << "\n"; + } + } return display_statistics(out); } diff --git a/src/math/grobner/pdd_solver.h b/src/math/grobner/pdd_solver.h index 150a70df8..0069eadf3 100644 --- a/src/math/grobner/pdd_solver.h +++ b/src/math/grobner/pdd_solver.h @@ -118,6 +118,7 @@ private: equation_vector m_solved; // equations with solved variables, triangular equation_vector m_processed; equation_vector m_to_simplify; + vector> m_subst; mutable u_dependency_manager m_dep_manager; equation_vector m_all_eqs; equation* m_conflict; @@ -136,6 +137,9 @@ public: void add(pdd const& p) { add(p, nullptr); } void add(pdd const& p, u_dependency * dep); + void simplify(pdd& p, u_dependency*& dep); + void add_subst(unsigned v, pdd const& p, u_dependency* dep); + void simplify(); void saturate(); @@ -160,6 +164,7 @@ private: void simplify_using(equation& eq, equation_vector const& eqs); void simplify_using(equation_vector& set, equation const& eq); void simplify_using(equation & dst, equation const& src, bool& changed_leading_term); + void simplify_using(equation_vector& set, std::function& simplifier); bool try_simplify_using(equation& target, equation const& source, bool& changed_leading_term); bool is_trivial(equation const& eq) const { return eq.poly().is_zero(); } diff --git a/src/math/interval/dep_intervals.h b/src/math/interval/dep_intervals.h index edc2da146..990816696 100644 --- a/src/math/interval/dep_intervals.h +++ b/src/math/interval/dep_intervals.h @@ -222,7 +222,6 @@ public: template void mul(const rational& r, const interval& a, interval& b) const { - if (r.is_zero()) return; m_imanager.mul(r.to_mpq(), a, b); if (wd == with_deps) { auto lower_dep = a.m_lower_dep; diff --git a/src/math/lp/CMakeLists.txt b/src/math/lp/CMakeLists.txt index ad21bea37..6ec8ba12d 100644 --- a/src/math/lp/CMakeLists.txt +++ b/src/math/lp/CMakeLists.txt @@ -34,6 +34,7 @@ z3_add_component(lp nla_basics_lemmas.cpp nla_common.cpp nla_core.cpp + nla_grobner.cpp nla_intervals.cpp nla_monotone_lemmas.cpp nla_order_lemmas.cpp diff --git a/src/math/lp/horner.cpp b/src/math/lp/horner.cpp index 82fb89b4e..4d4ac4975 100644 --- a/src/math/lp/horner.cpp +++ b/src/math/lp/horner.cpp @@ -40,7 +40,7 @@ bool horner::row_has_monomial_to_refine(const T& row) const { template bool horner::row_is_interesting(const T& row) const { TRACE("nla_solver_details", c().print_row(row, tout);); - if (row.size() > c().m_nla_settings.horner_row_length_limit()) { + if (row.size() > c().m_nla_settings.horner_row_length_limit) { TRACE("nla_solver_details", tout << "disregard\n";); return false; } @@ -98,7 +98,7 @@ bool horner::lemmas_on_row(const T& row) { } bool horner::horner_lemmas() { - if (!c().m_nla_settings.run_horner()) { + if (!c().m_nla_settings.run_horner) { TRACE("nla_solver", tout << "not generating horner lemmas\n";); return false; } diff --git a/src/math/lp/lar_solver.h b/src/math/lp/lar_solver.h index 0c61bdcb2..f1cbd3370 100644 --- a/src/math/lp/lar_solver.h +++ b/src/math/lp/lar_solver.h @@ -275,9 +275,6 @@ class lar_solver : public column_namer { return m_column_buffer; } bool bound_is_integer_for_integer_column(unsigned j, const mpq & right_side) const; - inline unsigned get_base_column_in_row(unsigned row_index) const { - return m_mpq_lar_core_solver.m_r_solver.get_base_column_in_row(row_index); - } inline lar_core_solver & get_core_solver() { return m_mpq_lar_core_solver; } void catch_up_in_updating_int_solver(); var_index to_column(unsigned ext_j) const; @@ -357,6 +354,10 @@ public: } void set_value_for_nbasic_column(unsigned j, const impq& new_val); + inline unsigned get_base_column_in_row(unsigned row_index) const { + return m_mpq_lar_core_solver.m_r_solver.get_base_column_in_row(row_index); + } + // lp_assert(implied_bound_is_correctly_explained(ib, explanation)); } constraint_index mk_var_bound(var_index j, lconstraint_kind kind, const mpq & right_side); @@ -630,6 +631,7 @@ public: } void round_to_integer_solution(); inline const row_strip & get_row(unsigned i) const { return A_r().m_rows[i]; } + inline const row_strip & basic2row(unsigned i) const { return A_r().m_rows[row_of_basic_column(i)]; } inline const column_strip & get_column(unsigned i) const { return A_r().m_columns[i]; } bool row_is_correct(unsigned i) const; bool ax_is_correct() const; diff --git a/src/math/lp/nla_common.cpp b/src/math/lp/nla_common.cpp index 8f635f391..45898c613 100644 --- a/src/math/lp/nla_common.cpp +++ b/src/math/lp/nla_common.cpp @@ -71,11 +71,11 @@ void common::add_deps_of_fixed(lpvar j, u_dependency*& dep) { // creates a nex expression for the coeff and var, nex * common::nexvar(const rational & coeff, lpvar j, nex_creator& cn, u_dependency*& dep) { SASSERT(!coeff.is_zero()); - if (c().m_nla_settings.horner_subs_fixed() == 1 && c().var_is_fixed(j)) { + if (c().m_nla_settings.horner_subs_fixed == 1 && c().var_is_fixed(j)) { add_deps_of_fixed(j, dep); return cn.mk_scalar(coeff * c().m_lar_solver.column_lower_bound(j).x); } - if (c().m_nla_settings.horner_subs_fixed() == 2 && c().var_is_fixed_to_zero(j)) { + if (c().m_nla_settings.horner_subs_fixed == 2 && c().var_is_fixed_to_zero(j)) { add_deps_of_fixed(j, dep); return cn.mk_scalar(rational(0)); } @@ -89,10 +89,10 @@ nex * common::nexvar(const rational & coeff, lpvar j, nex_creator& cn, u_depende mf *= coeff; u_dependency * initial_dep = dep; for (lpvar k : m.vars()) { - if (c().m_nla_settings.horner_subs_fixed() && c().var_is_fixed(k)) { + if (c().m_nla_settings.horner_subs_fixed == 1 && c().var_is_fixed(k)) { add_deps_of_fixed(k, dep); mf *= c().m_lar_solver.column_lower_bound(k).x; - } else if (c().m_nla_settings.horner_subs_fixed() == 2 && + } else if (c().m_nla_settings.horner_subs_fixed == 2 && c().var_is_fixed_to_zero(k)) { dep = initial_dep; add_deps_of_fixed(k, dep); diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index d8d4930ce..efd510824 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -1,2004 +1,1734 @@ - /*++ -Copyright (c) 2017 Microsoft Corporation - -Module Name: - - nla_core.cpp - -Author: - Lev Nachmanson (levnach) - Nikolaj Bjorner (nbjorner) - ---*/ -#include "util/uint_set.h" -#include "math/lp/nla_core.h" -#include "math/lp/factorization_factory_imp.h" -#include "math/lp/nex.h" -#include "math/grobner/pdd_solver.h" -#include "math/dd/pdd_interval.h" -#include "math/dd/pdd_eval.h" -namespace nla { - -typedef lp::lar_term term; - -core::core(lp::lar_solver& s, reslimit & lim) : - m_evars(), - m_lar_solver(s), - m_tangents(this), - m_basics(this), - m_order(this), - m_monotone(this), - m_intervals(this, lim), - m_monomial_bounds(this), - m_horner(this), - m_pdd_manager(s.number_of_vars()), - m_pdd_grobner(lim, m_pdd_manager), - m_emons(m_evars), - m_reslim(lim), - m_use_nra_model(false), - m_nra(s, m_nra_lim, *this) -{ - m_nlsat_delay = lp_settings().nlsat_delay(); -} - -bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const { - switch(cmp) { - case llc::LE: return ls <= rs; - case llc::LT: return ls < rs; - case llc::GE: return ls >= rs; - case llc::GT: return ls > rs; - case llc::EQ: return ls == rs; - case llc::NE: return ls != rs; - default: SASSERT(false); - }; - - return false; -} - -rational core::value(const lp::lar_term& r) const { - rational ret(0); - for (lp::lar_term::ival t : r) { - ret += t.coeff() * val(t.column()); - } - return ret; -} - -lp::lar_term core::subs_terms_to_columns(const lp::lar_term& t) const { - lp::lar_term r; - for (lp::lar_term::ival p : t) { - lpvar j = p.column(); - if (lp::tv::is_term(j)) - j = m_lar_solver.map_term_index_to_column_index(j); - r.add_monomial(p.coeff(), j); - } - return r; -} - -bool core::ineq_holds(const ineq& n) const { - return compare_holds(value(n.term()), n.cmp(), n.rs()); -} - -bool core::lemma_holds(const lemma& l) const { - for(const ineq &i : l.ineqs()) { - if (ineq_holds(i)) - return true; - } - return false; -} - -lpvar core::map_to_root(lpvar j) const { - return m_evars.find(j).var(); -} - -svector core::sorted_rvars(const factor& f) const { - if (f.is_var()) { - svector r; r.push_back(map_to_root(f.var())); - return r; - } - return m_emons[f.var()].rvars(); -} - -// the value of the factor is equal to the value of the variable multiplied -// by the canonize_sign -bool core::canonize_sign(const factor& f) const { - return f.sign() ^ (f.is_var()? canonize_sign(f.var()) : canonize_sign(m_emons[f.var()])); -} - -bool core::canonize_sign(lpvar j) const { - return m_evars.find(j).sign(); -} - -bool core::canonize_sign_is_correct(const monic& m) const { - bool r = false; - for (lpvar j : m.vars()) { - r ^= canonize_sign(j); - } - return r == m.rsign(); -} - -bool core::canonize_sign(const monic& m) const { - SASSERT(canonize_sign_is_correct(m)); - return m.rsign(); -} - -bool core::canonize_sign(const factorization& f) const { - bool r = false; - for (const factor & a : f) { - r ^= canonize_sign(a); - } - return r; -} - -void core::add_monic(lpvar v, unsigned sz, lpvar const* vs) { - m_add_buffer.resize(sz); - for (unsigned i = 0; i < sz; i++) { - lpvar j = vs[i]; - if (lp::tv::is_term(j)) - j = m_lar_solver.map_term_index_to_column_index(j); - m_add_buffer[i] = j; - } - m_emons.add(v, m_add_buffer); -} - -void core::push() { - TRACE("nla_solver_verbose", tout << "\n";); - m_emons.push(); -} - - -void core::pop(unsigned n) { - TRACE("nla_solver_verbose", tout << "n = " << n << "\n";); - m_emons.pop(n); - SASSERT(elists_are_consistent(false)); -} - -rational core::product_value(const monic& m) const { - rational r(1); - for (auto j : m.vars()) { - r *= m_lar_solver.get_column_value(j).x; - } - return r; -} - -// return true iff the monic value is equal to the product of the values of the factors -bool core::check_monic(const monic& m) const { - SASSERT((!m_lar_solver.column_is_int(m.var())) || m_lar_solver.get_column_value(m.var()).is_int()); - bool ret = product_value(m) == m_lar_solver.get_column_value(m.var()).x; - CTRACE("nla_solver_check_monic", !ret, print_monic(m, tout) << '\n';); - return ret; -} - - -template -std::ostream& core::print_product(const T & m, std::ostream& out) const { - bool first = true; - for (lpvar v : m) { - if (!first) out << "*"; else first = false; - if (lp_settings().print_external_var_name()) - out << "(" << m_lar_solver.get_variable_name(v) << "=" << val(v) << ")"; - else - out << "(j" << v << " = " << val(v) << ")"; - - } - return out; -} -template -std::string core::product_indices_str(const T & m) const { - std::stringstream out; - bool first = true; - for (lpvar v : m) { - if (!first) - out << "*"; - else - first = false; - out << "j" << v;; - } - return out.str(); -} - -std::ostream & core::print_factor(const factor& f, std::ostream& out) const { - if (f.sign()) - out << "- "; - if (f.is_var()) { - out << "VAR, " << pp(f.var()); - } else { - out << "MON, v" << m_emons[f.var()] << " = "; - print_product(m_emons[f.var()].rvars(), out); - } - out << "\n"; - return out; -} - -std::ostream & core::print_factor_with_vars(const factor& f, std::ostream& out) const { - if (f.is_var()) { - out << pp(f.var()); - } - else { - out << " MON = " << pp_mon_with_vars(*this, m_emons[f.var()]); - } - return out; -} - -std::ostream& core::print_monic(const monic& m, std::ostream& out) const { - if (lp_settings().print_external_var_name()) - out << "([" << m.var() << "] = " << m_lar_solver.get_variable_name(m.var()) << " = " << val(m.var()) << " = "; - else - out << "(j" << m.var() << " = " << val(m.var()) << " = "; - print_product(m.vars(), out) << ")\n"; - return out; -} - - -std::ostream& core::print_bfc(const factorization& m, std::ostream& out) const { - SASSERT(m.size() == 2); - out << "( x = " << pp(m[0]) << "* y = " << pp(m[1]) << ")"; - return out; -} - -std::ostream& core::print_monic_with_vars(lpvar v, std::ostream& out) const { - return print_monic_with_vars(m_emons[v], out); -} -template -std::ostream& core::print_product_with_vars(const T& m, std::ostream& out) const { - print_product(m, out) << "\n"; - for (unsigned k = 0; k < m.size(); k++) { - print_var(m[k], out); - } - return out; -} - -std::ostream& core::print_monic_with_vars(const monic& m, std::ostream& out) const { - out << "[" << pp(m.var()) << "]\n"; - out << "vars:"; print_product_with_vars(m.vars(), out) << "\n"; - if (m.vars() == m.rvars()) - out << "same rvars, and m.rsign = " << m.rsign() << " of course\n"; - else { - out << "rvars:"; print_product_with_vars(m.rvars(), out) << "\n"; - out << "rsign:" << m.rsign() << "\n"; - } - return out; -} - -std::ostream& core::print_explanation(const lp::explanation& exp, std::ostream& out) const { - out << "expl: "; - unsigned i = 0; - for (auto p : exp) { - out << "(" << p.ci() << ")"; - m_lar_solver.constraints().display(out, [this](lpvar j) { return var_str(j);}, p.ci()); - if (++i < exp.size()) - out << " "; - } - return out; -} - -bool core::explain_upper_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { - rational b(0); // the bound - for (lp::lar_term::ival p : t) { - rational pb; - if (explain_coeff_upper_bound(p, pb, e)) { - b += pb; - } else { - e.clear(); - return false; - } - } - if (b > rs ) { - e.clear(); - return false; - } - return true; -} -bool core::explain_lower_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { - rational b(0); // the bound - for (lp::lar_term::ival p : t) { - rational pb; - if (explain_coeff_lower_bound(p, pb, e)) { - b += pb; - } else { - e.clear(); - return false; - } - } - if (b < rs ) { - e.clear(); - return false; - } - return true; -} - -bool core::explain_coeff_lower_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { - const rational& a = p.coeff(); - SASSERT(!a.is_zero()); - unsigned c; // the index for the lower or the upper bound - if (a.is_pos()) { - unsigned c = m_lar_solver.get_column_lower_bound_witness(p.column()); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_lower_bound(p.column()).x; - e.push_back(c); - return true; - } - // a.is_neg() - c = m_lar_solver.get_column_upper_bound_witness(p.column()); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_upper_bound(p.column()).x; - e.push_back(c); - return true; -} - -bool core::explain_coeff_upper_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { - const rational& a = p.coeff(); - lpvar j = p.column(); - SASSERT(!a.is_zero()); - unsigned c; // the index for the lower or the upper bound - if (a.is_neg()) { - unsigned c = m_lar_solver.get_column_lower_bound_witness(j); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_lower_bound(j).x; - e.push_back(c); - return true; - } - // a.is_pos() - c = m_lar_solver.get_column_upper_bound_witness(j); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_upper_bound(j).x; - e.push_back(c); - return true; -} - -// return true iff the negation of the ineq can be derived from the constraints -bool core::explain_ineq(new_lemma& lemma, const lp::lar_term& t, llc cmp, const rational& rs) { - // check that we have something like 0 < 0, which is always false and can be safely - // removed from the lemma - - if (t.is_empty() && rs.is_zero() && - (cmp == llc::LT || cmp == llc::GT || cmp == llc::NE)) return true; - lp::explanation exp; - bool r; - switch (negate(cmp)) { - case llc::LE: - r = explain_upper_bound(t, rs, exp); - break; - case llc::LT: - r = explain_upper_bound(t, rs - rational(1), exp); - break; - case llc::GE: - r = explain_lower_bound(t, rs, exp); - break; - case llc::GT: - r = explain_lower_bound(t, rs + rational(1), exp); - break; - - case llc::EQ: - r = (explain_lower_bound(t, rs, exp) && explain_upper_bound(t, rs, exp)) || - (rs.is_zero() && explain_by_equiv(t, exp)); - break; - case llc::NE: - // TBD - NB: does this work for Reals? - r = explain_lower_bound(t, rs + rational(1), exp) || explain_upper_bound(t, rs - rational(1), exp); - break; - default: - UNREACHABLE(); - return false; - } - if (r) { - lemma &= exp; - return true; - } - - return false; -} - -/** - * \brief - if t is an octagon term -+x -+ y try to explain why the term always is - equal zero -*/ -bool core::explain_by_equiv(const lp::lar_term& t, lp::explanation& e) const { - lpvar i,j; - bool sign; - if (!is_octagon_term(t, sign, i, j)) - return false; - if (m_evars.find(signed_var(i, false)) != m_evars.find(signed_var(j, sign))) - return false; - - m_evars.explain(signed_var(i, false), signed_var(j, sign), e); - TRACE("nla_solver", tout << "explained :"; m_lar_solver.print_term_as_indices(t, tout);); - return true; -} - -void core::mk_ineq_no_expl_check(new_lemma& lemma, lp::lar_term& t, llc cmp, const rational& rs) { - TRACE("nla_solver_details", m_lar_solver.print_term_as_indices(t, tout << "t = ");); - lemma |= ineq(cmp, t, rs); - CTRACE("nla_solver", ineq_holds(ineq(cmp, t, rs)), print_ineq(ineq(cmp, t, rs), tout) << "\n";); - SASSERT(!ineq_holds(ineq(cmp, t, rs))); -} - -llc apply_minus(llc cmp) { - switch(cmp) { - case llc::LE: return llc::GE; - case llc::LT: return llc::GT; - case llc::GE: return llc::LE; - case llc::GT: return llc::LT; - default: break; - } - return cmp; -} - -// the monics should be equal by modulo sign but this is not so in the model -void core::fill_explanation_and_lemma_sign(new_lemma& lemma, const monic& a, const monic & b, rational const& sign) { - SASSERT(sign == 1 || sign == -1); - lemma &= a; - lemma &= b; - TRACE("nla_solver", tout << "used constraints: " << lemma;); - SASSERT(lemma.num_ineqs() == 0); - lemma |= ineq(term(rational(1), a.var(), -sign, b.var()), llc::EQ, 0); -} - -// Replaces each variable index by the root in the tree and flips the sign if the var comes with a minus. -// Also sorts the result. -// -svector core::reduce_monic_to_rooted(const svector & vars, rational & sign) const { - svector ret; - bool s = false; - for (lpvar v : vars) { - auto root = m_evars.find(v); - s ^= root.sign(); - TRACE("nla_solver_eq", - tout << pp(v) << " mapped to " << pp(root.var()) << "\n";); - ret.push_back(root.var()); - } - sign = rational(s? -1: 1); - std::sort(ret.begin(), ret.end()); - return ret; -} - - -// Replaces definition m_v = v1* .. * vn by -// m_v = coeff * w1 * ... * wn, where w1, .., wn are canonical -// representatives, which are the roots of the equivalence tree, under current equations. -// -monic_coeff core::canonize_monic(monic const& m) const { - rational sign = rational(1); - svector vars = reduce_monic_to_rooted(m.vars(), sign); - return monic_coeff(vars, sign); -} - -int core::vars_sign(const svector& v) { - int sign = 1; - for (lpvar j : v) { - sign *= nla::rat_sign(val(j)); - if (sign == 0) - return 0; - } - return sign; -} - -bool core::has_upper_bound(lpvar j) const { - return m_lar_solver.column_has_upper_bound(j); -} - -bool core::has_lower_bound(lpvar j) const { - return m_lar_solver.column_has_lower_bound(j); -} -const rational& core::get_upper_bound(unsigned j) const { - return m_lar_solver.get_upper_bound(j).x; -} - -const rational& core::get_lower_bound(unsigned j) const { - return m_lar_solver.get_lower_bound(j).x; -} - -bool core::zero_is_an_inner_point_of_bounds(lpvar j) const { - if (has_upper_bound(j) && get_upper_bound(j) <= rational(0)) - return false; - if (has_lower_bound(j) && get_lower_bound(j) >= rational(0)) - return false; - return true; -} - -int core::rat_sign(const monic& m) const { - int sign = 1; - for (lpvar j : m.vars()) { - auto v = val(j); - if (v.is_neg()) { - sign = - sign; - continue; - } - if (v.is_pos()) { - continue; - } - sign = 0; - break; - } - return sign; -} - -// Returns true if the monic sign is incorrect -bool core::sign_contradiction(const monic& m) const { - return nla::rat_sign(var_val(m)) != rat_sign(m); -} - -/* - unsigned_vector eq_vars(lpvar j) const { - TRACE("nla_solver_eq", tout << "j = " << pp(j) << "eqs = "; - for(auto jj : m_evars.eq_vars(j)) tout << pp(jj) << " "; - }); - return m_evars.eq_vars(j); - } -*/ - -bool core::var_is_fixed_to_zero(lpvar j) const { - return - m_lar_solver.column_is_fixed(j) && - m_lar_solver.get_lower_bound(j) == lp::zero_of_type(); -} -bool core::var_is_fixed_to_val(lpvar j, const rational& v) const { - return - m_lar_solver.column_is_fixed(j) && - m_lar_solver.get_lower_bound(j) == lp::impq(v); -} - -bool core::var_is_fixed(lpvar j) const { - return m_lar_solver.column_is_fixed(j); -} - -bool core::var_is_free(lpvar j) const { - return m_lar_solver.column_is_free(j); -} - -std::ostream & core::print_ineq(const ineq & in, std::ostream & out) const { - m_lar_solver.print_term_as_indices(in.term(), out); - out << " " << lconstraint_kind_string(in.cmp()) << " " << in.rs(); - return out; -} - -std::ostream & core::print_var(lpvar j, std::ostream & out) const { - if (m_emons.is_monic_var(j)) { - print_monic(m_emons[j], out); - } - - m_lar_solver.print_column_info(j, out); - signed_var jr = m_evars.find(j); - out << "root="; - if (jr.sign()) { - out << "-"; - } - - out << m_lar_solver.get_variable_name(jr.var()) << "\n"; - return out; -} - -std::ostream & core::print_monics(std::ostream & out) const { - for (auto &m : m_emons) { - print_monic_with_vars(m, out); - } - return out; -} - -std::ostream & core::print_ineqs(const lemma& l, std::ostream & out) const { - std::unordered_set vars; - out << "ineqs: "; - if (l.ineqs().size() == 0) { - out << "conflict\n"; - } else { - for (unsigned i = 0; i < l.ineqs().size(); i++) { - auto & in = l.ineqs()[i]; - print_ineq(in, out); - if (i + 1 < l.ineqs().size()) out << " or "; - for (lp::lar_term::ival p: in.term()) - vars.insert(p.column()); - } - out << std::endl; - for (lpvar j : vars) { - print_var(j, out); - } - out << "\n"; - } - return out; -} - -std::ostream & core::print_factorization(const factorization& f, std::ostream& out) const { - if (f.is_mon()){ - out << "is_mon " << pp_mon(*this, f.mon()); - } - else { - for (unsigned k = 0; k < f.size(); k++ ) { - out << "(" << pp(f[k]) << ")"; - if (k < f.size() - 1) - out << "*"; - } - } - return out; -} - -bool core::find_canonical_monic_of_vars(const svector& vars, lpvar & i) const { - monic const* sv = m_emons.find_canonical(vars); - return sv && (i = sv->var(), true); -} - -bool core::is_canonical_monic(lpvar j) const { - return m_emons.is_canonical_monic(j); -} - - -void core::trace_print_monic_and_factorization(const monic& rm, const factorization& f, std::ostream& out) const { - out << "rooted vars: "; - print_product(rm.rvars(), out) << "\n"; - out << "mon: " << pp_mon(*this, rm.var()) << "\n"; - out << "value: " << var_val(rm) << "\n"; - print_factorization(f, out << "fact: ") << "\n"; -} - - -bool core::var_has_positive_lower_bound(lpvar j) const { - return m_lar_solver.column_has_lower_bound(j) && m_lar_solver.get_lower_bound(j) > lp::zero_of_type(); -} - -bool core::var_has_negative_upper_bound(lpvar j) const { - return m_lar_solver.column_has_upper_bound(j) && m_lar_solver.get_upper_bound(j) < lp::zero_of_type(); -} - -bool core::var_is_separated_from_zero(lpvar j) const { - return - var_has_negative_upper_bound(j) || - var_has_positive_lower_bound(j); -} - - -bool core::vars_are_equiv(lpvar a, lpvar b) const { - SASSERT(abs(val(a)) == abs(val(b))); - return m_evars.vars_are_equiv(a, b); -} - -bool core::has_zero_factor(const factorization& factorization) const { - for (factor f : factorization) { - if (val(f).is_zero()) - return true; - } - return false; -} - - -template -bool core::mon_has_zero(const T& product) const { - for (lpvar j: product) { - if (val(j).is_zero()) - return true; - } - return false; -} - -template bool core::mon_has_zero(const unsigned_vector& product) const; - - -lp::lp_settings& core::lp_settings() { - return m_lar_solver.settings(); -} -const lp::lp_settings& core::lp_settings() const { - return m_lar_solver.settings(); -} - -unsigned core::random() { return lp_settings().random_next(); } - - -// we look for octagon constraints here, with a left part +-x +- y -void core::collect_equivs() { - const lp::lar_solver& s = m_lar_solver; - - for (unsigned i = 0; i < s.terms().size(); i++) { - if (!s.term_is_used_as_row(i)) - continue; - lpvar j = s.external_to_local(lp::tv::mask_term(i)); - if (var_is_fixed_to_zero(j)) { - TRACE("nla_solver_mons", s.print_term_as_indices(*s.terms()[i], tout << "term = ") << "\n";); - add_equivalence_maybe(s.terms()[i], s.get_column_upper_bound_witness(j), s.get_column_lower_bound_witness(j)); - } - } - m_emons.ensure_canonized(); -} - - -// returns true iff the term is in a form +-x-+y. -// the sign is true iff the term is x+y, -x-y. -bool core::is_octagon_term(const lp::lar_term& t, bool & sign, lpvar& i, lpvar &j) const { - if (t.size() != 2) - return false; - bool seen_minus = false; - bool seen_plus = false; - i = null_lpvar; - for(lp::lar_term::ival p : t) { - const auto & c = p.coeff(); - if (c == 1) { - seen_plus = true; - } else if (c == - 1) { - seen_minus = true; - } else { - return false; - } - if (i == null_lpvar) - i = p.column(); - else - j = p.column(); - } - SASSERT(j != null_lpvar); - sign = (seen_minus && seen_plus)? false : true; - return true; -} - -void core::add_equivalence_maybe(const lp::lar_term *t, lpci c0, lpci c1) { - bool sign; - lpvar i, j; - if (!is_octagon_term(*t, sign, i, j)) - return; - if (sign) - m_evars.merge_minus(i, j, eq_justification({c0, c1})); - else - m_evars.merge_plus(i, j, eq_justification({c0, c1})); -} - -// x is equivalent to y if x = +- y -void core::init_vars_equivalence() { - collect_equivs(); - // SASSERT(tables_are_ok()); -} - -bool core::vars_table_is_ok() const { - // return m_var_eqs.is_ok(); - return true; -} - -bool core::rm_table_is_ok() const { - // return m_emons.is_ok(); - return true; -} - -bool core::tables_are_ok() const { - return vars_table_is_ok() && rm_table_is_ok(); -} - -bool core::var_is_a_root(lpvar j) const { return m_evars.is_root(j); } - -template -bool core::vars_are_roots(const T& v) const { - for (lpvar j: v) { - if (!var_is_a_root(j)) - return false; - } - return true; -} - - - -template -void core::trace_print_rms(const T& p, std::ostream& out) { - out << "p = {\n"; - for (auto j : p) { - out << "j = " << j << ", rm = " << m_emons[j] << "\n"; - } - out << "}"; -} - -void core::print_monic_stats(const monic& m, std::ostream& out) { - if (m.size() == 2) return; - monic_coeff mc = canonize_monic(m); - for(unsigned i = 0; i < mc.vars().size(); i++){ - if (abs(val(mc.vars()[i])) == rational(1)) { - auto vv = mc.vars(); - vv.erase(vv.begin()+i); - monic const* sv = m_emons.find_canonical(vv); - if (!sv) { - out << "nf length" << vv.size() << "\n"; ; - } - } - } -} - -void core::print_stats(std::ostream& out) { -} - - -void core::clear() { - m_lemma_vec->clear(); -} - -void core::init_search() { - TRACE("nla_solver_mons", tout << "init\n";); - SASSERT(m_emons.invariant()); - clear(); - init_vars_equivalence(); - SASSERT(m_emons.invariant()); - SASSERT(elists_are_consistent(false)); -} - -void core::insert_to_refine(lpvar j) { - TRACE("lar_solver", tout << "j=" << j << '\n';); - m_to_refine.insert(j); -} - -void core::erase_from_to_refine(lpvar j) { - TRACE("lar_solver", tout << "j=" << j << '\n';); - m_to_refine.erase(j); -} - - -void core::init_to_refine() { - TRACE("nla_solver_details", tout << "emons:" << pp_emons(*this, m_emons);); - m_to_refine.clear(); - m_to_refine.resize(m_lar_solver.number_of_vars()); - unsigned r = random(), sz = m_emons.number_of_monics(); - for (unsigned k = 0; k < sz; k++) { - auto const & m = *(m_emons.begin() + (k + r)% sz); - if (!check_monic(m)) - insert_to_refine(m.var()); - } - - TRACE("nla_solver", - tout << m_to_refine.size() << " mons to refine:\n"; - for (lpvar v : m_to_refine) tout << pp_mon(*this, m_emons[v]) << ":error = " << - (val(v) - mul_val(m_emons[v])).get_double() << "\n";); -} - -std::unordered_set core::collect_vars(const lemma& l) const { - std::unordered_set vars; - auto insert_j = [&](lpvar j) { - vars.insert(j); - if (m_emons.is_monic_var(j)) { - for (lpvar k : m_emons[j].vars()) - vars.insert(k); - } - }; - - for (const auto& i : l.ineqs()) { - for (lp::lar_term::ival p : i.term()) { - insert_j(p.column()); - } - } - for (auto p : l.expl()) { - const auto& c = m_lar_solver.constraints()[p.ci()]; - for (const auto& r : c.coeffs()) { - insert_j(r.second); - } - } - return vars; -} - -// divides bc by c, so bc = b*c -bool core::divide(const monic& bc, const factor& c, factor & b) const { - svector c_rvars = sorted_rvars(c); - TRACE("nla_solver_div", tout << "c_rvars = "; print_product(c_rvars, tout); tout << "\nbc_rvars = "; print_product(bc.rvars(), tout);); - if (!lp::is_proper_factor(c_rvars, bc.rvars())) - return false; - - auto b_rvars = lp::vector_div(bc.rvars(), c_rvars); - TRACE("nla_solver_div", tout << "b_rvars = "; print_product(b_rvars, tout);); - SASSERT(b_rvars.size() > 0); - if (b_rvars.size() == 1) { - b = factor(b_rvars[0], factor_type::VAR); - } else { - monic const* sv = m_emons.find_canonical(b_rvars); - if (sv == nullptr) { - TRACE("nla_solver_div", tout << "not in rooted";); - return false; - } - b = factor(sv->var(), factor_type::MON); - } - SASSERT(!b.sign()); - // We have bc = canonize_sign(bc)*bc.rvars() = canonize_sign(b)*b.rvars()*canonize_sign(c)*c.rvars(). - // Dividing by bc.rvars() we get canonize_sign(bc) = canonize_sign(b)*canonize_sign(c) - // Currently, canonize_sign(b) is 1, we might need to adjust it - b.sign() = canonize_sign(b) ^ canonize_sign(c) ^ canonize_sign(bc); - TRACE("nla_solver", tout << "success div:" << pp(b) << "\n";); - return true; -} - - -void core::negate_factor_equality(new_lemma& lemma, const factor& c, - const factor& d) { - if (c == d) - return; - lpvar i = var(c); - lpvar j = var(d); - auto iv = val(i), jv = val(j); - SASSERT(abs(iv) == abs(jv)); - lemma |= ineq(term(i, rational(iv == jv ? -1 : 1), j), llc::NE, 0); -} - -void core::negate_factor_relation(new_lemma& lemma, const rational& a_sign, const factor& a, const rational& b_sign, const factor& b) { - rational a_fs = sign_to_rat(canonize_sign(a)); - rational b_fs = sign_to_rat(canonize_sign(b)); - llc cmp = a_sign*val(a) < b_sign*val(b)? llc::GE : llc::LE; - lemma |= ineq(term(a_fs*a_sign, var(a), - b_fs*b_sign, var(b)), cmp, 0); -} - -std::ostream& core::print_lemma(const lemma& l, std::ostream& out) const { - static int n = 0; - out << "lemma:" << ++n << " "; - print_ineqs(l, out); - print_explanation(l.expl(), out); - for (lpvar j : collect_vars(l)) { - print_var(j, out); - } - return out; -} - - -void core::trace_print_ol(const monic& ac, - const factor& a, - const factor& c, - const monic& bc, - const factor& b, - std::ostream& out) { - out << "ac = " << pp_mon(*this, ac) << "\n"; - out << "bc = " << pp_mon(*this, bc) << "\n"; - out << "a = "; - print_factor_with_vars(a, out); - out << ", \nb = "; - print_factor_with_vars(b, out); - out << "\nc = "; - print_factor_with_vars(c, out); -} - -void core::maybe_add_a_factor(lpvar i, - const factor& c, - std::unordered_set& found_vars, - std::unordered_set& found_rm, - vector & r) const { - SASSERT(abs(val(i)) == abs(val(c))); - if (!m_emons.is_monic_var(i)) { - i = m_evars.find(i).var(); - if (try_insert(i, found_vars)) { - r.push_back(factor(i, factor_type::VAR)); - } - } else { - if (try_insert(i, found_rm)) { - r.push_back(factor(i, factor_type::MON)); - TRACE("nla_solver", tout << "inserting factor = "; print_factor_with_vars(factor(i, factor_type::MON), tout); ); - } - } -} - - -// Returns rooted monics by arity -std::unordered_map core::get_rm_by_arity() { - std::unordered_map m; - for (auto const& mon : m_emons) { - unsigned arity = mon.vars().size(); - auto it = m.find(arity); - if (it == m.end()) { - it = m.insert(it, std::make_pair(arity, unsigned_vector())); - } - it->second.push_back(mon.var()); - } - return m; -} - -bool core::rm_check(const monic& rm) const { - return check_monic(m_emons[rm.var()]); -} - - -bool core::find_bfc_to_refine_on_monic(const monic& m, factorization & bf) { - for (auto f : factorization_factory_imp(m, *this)) { - if (f.size() == 2) { - auto a = f[0]; - auto b = f[1]; - if (var_val(m) != val(a) * val(b)) { - bf = f; - TRACE("nla_solver", tout << "found bf"; - tout << ":m:" << pp_mon_with_vars(*this, m) << "\n"; - tout << "bf:"; print_bfc(bf, tout);); - - return true; - } - } - } - return false; -} - -// finds a monic to refine with its binary factorization -bool core::find_bfc_to_refine(const monic* & m, factorization & bf){ - m = nullptr; - unsigned r = random(), sz = m_to_refine.size(); - for (unsigned k = 0; k < sz; k++) { - lpvar i = m_to_refine[(k + r) % sz]; - m = &m_emons[i]; - SASSERT (!check_monic(*m)); - if (has_real(m)) - continue; - if (m->size() == 2) { - bf.set_mon(m); - bf.push_back(factor(m->vars()[0], factor_type::VAR)); - bf.push_back(factor(m->vars()[1], factor_type::VAR)); - return true; - } - - if (find_bfc_to_refine_on_monic(*m, bf)) { - TRACE("nla_solver", - tout << "bf = "; print_factorization(bf, tout); - tout << "\nval(*m) = " << var_val(*m) << ", should be = (val(bf[0])=" << val(bf[0]) << ")*(val(bf[1]) = " << val(bf[1]) << ") = " << val(bf[0])*val(bf[1]) << "\n";); - return true; - } - } - return false; -} - -rational core::val(const factorization& f) const { - rational r(1); - for (const factor &p : f) { - r *= val(p); - } - return r; -} - -new_lemma::new_lemma(core& c, char const* name):name(name), c(c) { - c.m_lemma_vec->push_back(lemma()); -} - -new_lemma& new_lemma::operator|=(ineq const& ineq) { - if (!c.explain_ineq(*this, ineq.term(), ineq.cmp(), ineq.rs())) { - CTRACE("nla_solver", c.ineq_holds(ineq), c.print_ineq(ineq, tout) << "\n";); - SASSERT(!c.ineq_holds(ineq)); - current().push_back(ineq); - } - return *this; -} - - -new_lemma::~new_lemma() { - static int i = 0; - (void)i; - (void)name; - // code for checking lemma can be added here - TRACE("nla_solver", tout << name << " " << (++i) << "\n" << *this; ); -} - -lemma& new_lemma::current() const { - return c.m_lemma_vec->back(); -} - -new_lemma& new_lemma::operator&=(lp::explanation const& e) { - expl().add_expl(e); - return *this; -} - -new_lemma& new_lemma::operator&=(const monic& m) { - for (lpvar j : m.vars()) - *this &= j; - return *this; -} - -new_lemma& new_lemma::operator&=(const factor& f) { - if (f.type() == factor_type::VAR) - *this &= f.var(); - else - *this &= c.m_emons[f.var()]; - return *this; -} - -new_lemma& new_lemma::operator&=(const factorization& f) { - if (f.is_mon()) - return *this; - for (const auto& fc : f) { - *this &= fc; - } - return *this; -} - -new_lemma& new_lemma::operator&=(lpvar j) { - c.m_evars.explain(j, expl()); - return *this; -} - -new_lemma& new_lemma::explain_fixed(lpvar j) { - SASSERT(c.var_is_fixed(j)); - explain_existing_lower_bound(j); - explain_existing_upper_bound(j); - return *this; -} - -new_lemma& new_lemma::explain_equiv(lpvar a, lpvar b) { - SASSERT(abs(c.val(a)) == abs(c.val(b))); - if (c.vars_are_equiv(a, b)) { - *this &= a; - *this &= b; - } else { - explain_fixed(a); - explain_fixed(b); - } - return *this; -} - -new_lemma& new_lemma::explain_var_separated_from_zero(lpvar j) { - SASSERT(c.var_is_separated_from_zero(j)); - if (c.m_lar_solver.column_has_upper_bound(j) && - (c.m_lar_solver.get_upper_bound(j)< lp::zero_of_type())) - explain_existing_upper_bound(j); - else - explain_existing_lower_bound(j); - return *this; -} - -new_lemma& new_lemma::explain_existing_lower_bound(lpvar j) { - SASSERT(c.has_lower_bound(j)); - lp::explanation ex; - ex.push_back(c.m_lar_solver.get_column_lower_bound_witness(j)); - *this &= ex; - TRACE("nla_solver", tout << j << ": " << *this << "\n";); - return *this; -} - -new_lemma& new_lemma::explain_existing_upper_bound(lpvar j) { - SASSERT(c.has_upper_bound(j)); - lp::explanation ex; - ex.push_back(c.m_lar_solver.get_column_upper_bound_witness(j)); - *this &= ex; - return *this; -} - -std::ostream& new_lemma::display(std::ostream & out) const { - auto const& lemma = current(); - - for (auto p : lemma.expl()) { - out << "(" << p.ci() << ") "; - c.m_lar_solver.constraints().display(out, [this](lpvar j) { return c.var_str(j);}, p.ci()); - } - out << " ==> "; - if (lemma.ineqs().empty()) { - out << "false"; - } - else { - bool first = true; - for (auto & in : lemma.ineqs()) { - if (first) first = false; else out << " or "; - c.print_ineq(in, out); - } - } - out << "\n"; - for (lpvar j : c.collect_vars(lemma)) { - c.print_var(j, out); - } - return out; -} - -void core::negate_relation(new_lemma& lemma, unsigned j, const rational& a) { - SASSERT(val(j) != a); - lemma |= ineq(j, val(j) < a ? llc::GE : llc::LE, a); -} - -bool core::conflict_found() const { - for (const auto & l : * m_lemma_vec) { - if (l.is_conflict()) - return true; - } - return false; -} - -bool core::done() const { - return m_lemma_vec->size() >= 10 || - conflict_found() || - lp_settings().get_cancel_flag(); -} - -bool core::elist_is_consistent(const std::unordered_set & list) const { - bool first = true; - bool p; - for (lpvar j : list) { - if (first) { - p = check_monic(m_emons[j]); - first = false; - } else - if (check_monic(m_emons[j]) != p) - return false; - } - return true; -} - -bool core::elists_are_consistent(bool check_in_model) const { - std::unordered_map, hash_svector> lists; - if (!m_emons.elists_are_consistent(lists)) - return false; - - if (!check_in_model) - return true; - for (const auto & p : lists) { - if (! elist_is_consistent(p.second)) - return false; - } - return true; -} - -bool core::var_breaks_correct_monic_as_factor(lpvar j, const monic& m) const { - if (!val(var(m)).is_zero()) - return true; - - if (!val(j).is_zero()) // j was not zero: the new value does not matter - m must have another zero factor - return false; - // do we have another zero in m? - for (lpvar k : m) { - if (k != j && val(k).is_zero()) { - return false; // not breaking - } - } - // j was the only zero in m - return true; -} - -bool core::var_breaks_correct_monic(lpvar j) const { - if (emons().is_monic_var(j) && !m_to_refine.contains(j)) { - TRACE("nla_solver", tout << "j = " << j << ", m = "; print_monic(emons()[j], tout) << "\n";); - return true; // changing the value of a correct monic - } - - for (const monic & m : emons().get_use_list(j)) { - if (m_to_refine.contains(m.var())) - continue; - if (var_breaks_correct_monic_as_factor(j, m)) - return true; - } - - return false; -} - -void core::update_to_refine_of_var(lpvar j) { - for (const monic & m : emons().get_use_list(j)) { - if (var_val(m) == mul_val(m)) - erase_from_to_refine(var(m)); - else - insert_to_refine(var(m)); - } - if (is_monic_var(j)) { - const monic& m = emons()[j]; - if (var_val(m) == mul_val(m)) - erase_from_to_refine(j); - else - insert_to_refine(j); - } -} - -bool core::var_is_big(lpvar j) const { - return !var_is_int(j) && val(j).is_big(); -} - -bool core::has_big_num(const monic& m) const { - if (var_is_big(var(m))) - return true; - for (lpvar j : m.vars()) - if (var_is_big(j)) - return true; - return false; -} - -bool core::has_real(const factorization& f) const { - for (const factor& fc: f) { - lpvar j = var(fc); - if (!var_is_int(j)) - return true; - } - return false; -} - -bool core::has_real(const monic& m) const { - for (lpvar j : m.vars()) - if (!var_is_int(j)) - return true; - return false; -} - -// returns true if the patching is blocking -bool core::is_patch_blocked(lpvar u, const lp::impq& ival) const { - TRACE("nla_solver", tout << "u = " << u << '\n';); - if (m_cautious_patching && - (!m_lar_solver.inside_bounds(u, ival) || (var_is_int(u) && ival.is_int() == false))) { - TRACE("nla_solver", tout << "u = " << u << " blocked, for feas or integr\n";); - return true; // block - } - - if (u == m_patched_var) { - TRACE("nla_solver", tout << "u == m_patched_var, no block\n";); - - return false; // do not block - } - // we can change only one variable in variables of m_patched_var - if (m_patched_monic->contains_var(u) || u == var(*m_patched_monic)) { - TRACE("nla_solver", tout << "u = " << u << " blocked as contained\n";); - return true; // block - } - - if (var_breaks_correct_monic(u)) { - TRACE("nla_solver", tout << "u = " << u << " blocked as used in a correct monomial\n";); - return true; - } - - TRACE("nla_solver", tout << "u = " << u << ", m_patched_m = "; print_monic(*m_patched_monic, tout) << - ", not blocked\n";); - - return false; -} - -// it tries to patch m_patched_var -bool core::try_to_patch(const rational& v) { - auto is_blocked = [this](lpvar u, const lp::impq& iv) { return is_patch_blocked(u, iv); }; - auto change_report = [this](lpvar u) { update_to_refine_of_var(u); }; - return m_lar_solver.try_to_patch(m_patched_var, v, is_blocked, change_report); -} - -bool in_power(const svector& vs, unsigned l) { - unsigned k = vs[l]; - return (l != 0 && vs[l - 1] == k) || (l + 1 < vs.size() && k == vs[l + 1]); -} - -bool core::to_refine_is_correct() const { - for (unsigned j = 0; j < m_lar_solver.number_of_vars(); j++) { - if (!emons().is_monic_var(j)) continue; - bool valid = check_monic(emons()[j]); - if (valid == m_to_refine.contains(j)) { - TRACE("nla_solver", tout << "inconstency in m_to_refine : "; - print_monic(emons()[j], tout) << "\n"; - if (valid) tout << "should NOT be in to_refine\n"; - else tout << "should be in to_refine\n";); - return false; - } - } - return true; -} - -void core::patch_monomial(lpvar j) { - m_patched_monic =& (emons()[j]); - m_patched_var = j; - TRACE("nla_solver", tout << "m = "; print_monic(*m_patched_monic, tout) << "\n";); - rational v = mul_val(*m_patched_monic); - if (val(j) == v) { - erase_from_to_refine(j); - return; - } - if (!var_breaks_correct_monic(j) && try_to_patch(v)) { - SASSERT(to_refine_is_correct()); - return; - } - - // We could not patch j, now we try patching the factor variables. - TRACE("nla_solver", tout << " trying squares\n";); - // handle perfect squares - if ((*m_patched_monic).vars().size() == 2 && (*m_patched_monic).vars()[0] == (*m_patched_monic).vars()[1]) { - rational root; - if (v.is_perfect_square(root)) { - m_patched_var = (*m_patched_monic).vars()[0]; - if (!var_breaks_correct_monic(m_patched_var) && (try_to_patch(root) || try_to_patch(-root))) { - TRACE("nla_solver", tout << "patched square\n";); - return; - } - } - TRACE("nla_solver", tout << " cannot patch\n";); - return; - } - - // We have v != abc, but we need to have v = abc. - // If we patch b then b should be equal to v/ac = v/(abc/b) = b(v/abc) - if (!v.is_zero()) { - rational r = val(j) / v; - SASSERT((*m_patched_monic).is_sorted()); - TRACE("nla_solver", tout << "r = " << r << ", v = " << v << "\n";); - for (unsigned l = 0; l < (*m_patched_monic).size(); l++) { - m_patched_var = (*m_patched_monic).vars()[l]; - if (!in_power((*m_patched_monic).vars(), l) && - !var_breaks_correct_monic(m_patched_var) && - try_to_patch(r * val(m_patched_var))) { // r * val(k) gives the right value of k - TRACE("nla_solver", tout << "patched " << m_patched_var << "\n";); - SASSERT(mul_val((*m_patched_monic)) == val(j)); - erase_from_to_refine(j); - break; - } - } - } -} - -void core::patch_monomials_on_to_refine() { - auto to_refine = m_to_refine.index(); - // the rest of the function might change m_to_refine, so have to copy - unsigned sz = to_refine.size(); - - unsigned start = random(); - for (unsigned i = 0; i < sz; i++) { - patch_monomial(to_refine[(start + i) % sz]); - if (m_to_refine.size() == 0) - break; - } - TRACE("nla_solver", tout << "sz = " << sz << ", m_to_refine = " << m_to_refine.size() << - (sz > m_to_refine.size()? " less" : "same" ) << "\n";); -} - -void core::patch_monomials() { - m_cautious_patching = true; - patch_monomials_on_to_refine(); - if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching()) { - return; - } - NOT_IMPLEMENTED_YET(); - m_cautious_patching = false; - patch_monomials_on_to_refine(); - m_lar_solver.push(); - save_tableau(); - constrain_nl_in_tableau(); - if (solve_tableau() && integrality_holds()) { - m_lar_solver.pop(1); - } else { - m_lar_solver.pop(); - restore_tableau(); - m_lar_solver.clear_inf_set(); - } - SASSERT(m_lar_solver.ax_is_correct()); -} - -void core::constrain_nl_in_tableau() { - NOT_IMPLEMENTED_YET(); -} - -bool core::solve_tableau() { - NOT_IMPLEMENTED_YET(); - return false; -} - -void core::restore_tableau() { - NOT_IMPLEMENTED_YET(); -} - -void core::save_tableau() { - NOT_IMPLEMENTED_YET(); -} - -bool core::integrality_holds() { - NOT_IMPLEMENTED_YET(); - return false; -} - -/** - * Cycle through different end-game solvers weighted by probability. - */ -void core::check_weighted(unsigned sz, std::pair>* checks) { - unsigned bound = 0; - for (unsigned i = 0; i < sz; ++i) - bound += checks[i].first; - uint_set seen; - while (bound > 0 && !done() && m_lemma_vec->empty()) { - unsigned n = random() % bound; - for (unsigned i = 0; i < sz; ++i) { - if (seen.contains(i)) - continue; - if (n < checks[i].first) { - seen.insert(i); - checks[i].second(); - bound -= checks[i].first; - break; - } - n -= checks[i].first; - } - } -} - - -lbool core::check(vector& l_vec) { - lp_settings().stats().m_nla_calls++; - TRACE("nla_solver", tout << "calls = " << lp_settings().stats().m_nla_calls << "\n";); - m_lar_solver.get_rid_of_inf_eps(); - m_lemma_vec = &l_vec; - if (!(m_lar_solver.get_status() == lp::lp_status::OPTIMAL || - m_lar_solver.get_status() == lp::lp_status::FEASIBLE)) { - TRACE("nla_solver", tout << "unknown because of the m_lar_solver.m_status = " << m_lar_solver.get_status() << "\n";); - return l_undef; - } - - init_to_refine(); - patch_monomials(); - set_use_nra_model(false); - if (m_to_refine.empty()) { return l_true; } - init_search(); - - lbool ret = l_undef; - - if (l_vec.empty() && !done()) - m_monomial_bounds(); - - if (l_vec.empty() && !done() && need_run_horner()) - m_horner.horner_lemmas(); - - if (l_vec.empty() && !done() && need_run_grobner()) - run_grobner(); - - if (l_vec.empty() && !done()) - m_basics.basic_lemma(true); - - if (l_vec.empty() && !done()) - m_basics.basic_lemma(false); - - if (!conflict_found() && !done() && should_run_bounded_nlsat()) - ret = bounded_nlsat(); - - - if (l_vec.empty() && !done() && ret == l_undef) { - std::function check1 = [&]() { m_order.order_lemma(); }; - std::function check2 = [&]() { m_monotone.monotonicity_lemma(); }; - std::function check3 = [&]() { m_tangents.tangent_lemma(); }; - - std::pair> checks[] = - { { 6, check1 }, - { 2, check2 }, - { 1, check3 }}; - check_weighted(3, checks); - - unsigned num_calls = lp_settings().stats().m_nla_calls; - if (!conflict_found() && m_nla_settings.run_nra() && num_calls % 50 == 0 && num_calls > 500) - ret = bounded_nlsat(); - } - - if (l_vec.empty() && !done() && m_nla_settings.run_nra() && ret == l_undef) { - ret = m_nra.check(); - m_stats.m_nra_calls++; - } - - if (ret == l_undef && !l_vec.empty() && m_reslim.inc()) - ret = l_false; - - m_stats.m_nla_lemmas += l_vec.size(); - for (const auto& l : l_vec) - m_stats.m_nla_explanations += static_cast(l.expl().size()); - - - TRACE("nla_solver", tout << "ret = " << ret << ", lemmas count = " << l_vec.size() << "\n";); - IF_VERBOSE(2, if(ret == l_undef) {verbose_stream() << "Monomials\n"; print_monics(verbose_stream());}); - CTRACE("nla_solver", ret == l_undef, tout << "Monomials\n"; print_monics(tout);); - return ret; -} - -bool core::should_run_bounded_nlsat() { - if (!m_nla_settings.run_nra()) - return false; - if (m_nlsat_delay > m_nlsat_fails) - ++m_nlsat_fails; - return m_nlsat_delay <= m_nlsat_fails; -} - -lbool core::bounded_nlsat() { - params_ref p; - lbool ret; - p.set_uint("max_conflicts", 100); - m_nra.updt_params(p); - { - scoped_limits sl(m_reslim); - sl.push_child(&m_nra_lim); - scoped_rlimit sr(m_nra_lim, 100000); - ret = m_nra.check(); - } - p.set_uint("max_conflicts", UINT_MAX); - m_nra.updt_params(p); - m_stats.m_nra_calls++; - if (ret == l_undef) - ++m_nlsat_delay; - else { - m_nlsat_fails = 0; - m_nlsat_delay /= 2; - } - if (ret == l_true) { - m_lemma_vec->reset(); - } - return ret; -} - -bool core::no_lemmas_hold() const { - for (auto & l : * m_lemma_vec) { - if (lemma_holds(l)) { - TRACE("nla_solver", print_lemma(l, tout);); - return false; - } - } - return true; -} - -lbool core::test_check(vector& l) { - m_lar_solver.set_status(lp::lp_status::OPTIMAL); - return check(l); -} - -std::ostream& core::print_terms(std::ostream& out) const { - for (unsigned i = 0; i< m_lar_solver.terms().size(); i++) { - unsigned ext = lp::tv::mask_term(i); - if (!m_lar_solver.var_is_registered(ext)) { - out << "term is not registered\n"; - continue; - } - - const lp::lar_term & t = *m_lar_solver.terms()[i]; - out << "term:"; print_term(t, out) << std::endl; - lpvar j = m_lar_solver.external_to_local(ext); - print_var(j, out); - } - return out; -} - -std::string core::var_str(lpvar j) const { - return is_monic_var(j)? - (product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_")) : (std::string("j") + lp::T_to_string(j)); -} - -std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const { - return lp::print_linear_combination_customized( - t.coeffs_as_vector(), - [this](lpvar j) { return var_str(j); }, - out); -} - - -void core::run_grobner() { - unsigned& quota = m_nla_settings.grobner_quota(); - if (quota == 1) { - return; - } - clear_and_resize_active_var_set(); - find_nl_cluster(); - - lp_settings().stats().m_grobner_calls++; - configure_grobner(); - m_pdd_grobner.saturate(); - bool conflict = false; - unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); - SASSERT(n > 0); - for (auto eq : m_pdd_grobner.equations()) { - if (check_pdd_eq(eq)) { - conflict = true; - if (--n == 0) - break; - } - } - if (conflict) { - IF_VERBOSE(2, verbose_stream() << "grobner conflict\n"); - return; - } - -#if 0 - bool propagated = false; - for (auto eq : m_pdd_grobner.equations()) { - auto const& p = eq->poly(); - if (p.is_offset()) { - lpvar v = p.var(); - if (m_lar_solver.column_has_lower_bound(v) && - m_lar_solver.column_has_upper_bound(v)) - continue; - rational fixed_val = -p.lo().val(); - lp::explanation ex; - u_dependency_manager dm; - vector lv; - dm.linearize(eq->dep(), lv); - for (unsigned ci : lv) - ex.push_back(ci); - new_lemma lemma(*this, "pdd-eq"); - lemma &= ex; - lemma |= ineq(v, llc::EQ, fixed_val); - propagated = true; - } - } - if (propagated) - return; -#endif - - if (quota > 1) - quota--; - IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); - IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); - -} - -void core::configure_grobner() { - m_pdd_grobner.reset(); - try { - set_level2var_for_grobner(); - for (unsigned i : m_rows) { - add_row_to_grobner(m_lar_solver.A_r().m_rows[i]); - } - } - catch (...) { - IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); - return; - } -#if 0 - IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); - dd::pdd_eval eval(m_pdd_manager); - eval.var2val() = [&](unsigned j){ return val(j); }; - for (auto* e : m_pdd_grobner.equations()) { - dd::pdd p = e->poly(); - rational v = eval(p); - if (p.is_linear() && !eval(p).is_zero()) { - IF_VERBOSE(0, verbose_stream() << "violated linear constraint " << p << "\n"); - } - } -#endif - - struct dd::solver::config cfg; - cfg.m_max_steps = m_pdd_grobner.equations().size(); - cfg.m_max_simplified = m_nla_settings.grobner_max_simplified(); - cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth(); - cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth(); - cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth(); - cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report(); - m_pdd_grobner.set(cfg); - m_pdd_grobner.adjust_cfg(); - m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. -} - -std::ostream& core::diagnose_pdd_miss(std::ostream& out) { - - // m_pdd_grobner.display(out); - - dd::pdd_eval eval; - eval.var2val() = [&](unsigned j){ return val(j); }; - for (auto* e : m_pdd_grobner.equations()) { - dd::pdd p = e->poly(); - rational v = eval(p); - if (!v.is_zero()) { - out << p << " := " << v << "\n"; - } - } - - for (unsigned j = 0; j < m_lar_solver.number_of_vars(); ++j) { - if (m_lar_solver.column_has_lower_bound(j) || m_lar_solver.column_has_upper_bound(j)) { - out << j << ": ["; - if (m_lar_solver.column_has_lower_bound(j)) out << m_lar_solver.get_lower_bound(j); - out << ".."; - if (m_lar_solver.column_has_upper_bound(j)) out << m_lar_solver.get_upper_bound(j); - out << "]\n"; - } - } - return out; -} - -bool core::check_pdd_eq(const dd::solver::equation* e) { - auto& di = m_intervals.get_dep_intervals(); - dd::pdd_interval eval(di); - eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { - if (deps) m_intervals.set_var_interval(j, a); - else m_intervals.set_var_interval(j, a); - }; - scoped_dep_interval i(di), i_wd(di); - eval.get_interval(e->poly(), i); - if (!di.separated_from_zero(i)) - return false; - eval.get_interval(e->poly(), i_wd); - std::function f = [this](const lp::explanation& e) { - new_lemma lemma(*this, "pdd"); - lemma &= e; - }; - if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { - lp_settings().stats().m_grobner_conflicts++; - return true; - } - else { - return false; - } -} - -void core::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { - if (active_var_set_contains(j) || var_is_fixed(j)) return; - TRACE("grobner", tout << "j = " << j << ", " << pp(j);); - const auto& matrix = m_lar_solver.A_r(); - insert_to_active_var_set(j); - for (auto & s : matrix.m_columns[j]) { - unsigned row = s.var(); - if (m_rows.contains(row)) continue; - if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit()) { - TRACE("grobner", tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); - continue; - } - m_rows.insert(row); - for (auto& rc : matrix.m_rows[row]) { - add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); - } - } - - if (!is_monic_var(j)) - return; - - const monic& m = emons()[j]; - for (auto fcn : factorization_factory_imp(m, *this)) { - for (const factor& fc: fcn) { - q.push_back(var(fc)); - } - } -} - -const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { - unsigned lc, uc; - m_lar_solver.get_bound_constraint_witnesses_for_column(j, lc, uc); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(lc)); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(uc)); - return m_lar_solver.column_lower_bound(j).x; -} - -dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { - if (m_nla_settings.grobner_subs_fixed() == 1 && var_is_fixed(j)) { - return m_pdd_manager.mk_val(c * val_of_fixed_var_with_deps(j, dep)); - } - - if (m_nla_settings.grobner_subs_fixed() == 2 && var_is_fixed_to_zero(j)) { - return m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, dep)); - } - - if (!is_monic_var(j)) - return c * m_pdd_manager.mk_var(j); - - u_dependency* zero_dep = dep; - // j is a monic var - dd::pdd r = m_pdd_manager.mk_val(c); - const monic& m = emons()[j]; - for (lpvar k : m.vars()) { - if (m_nla_settings.grobner_subs_fixed() && var_is_fixed(k)) { - r *= m_pdd_manager.mk_val(val_of_fixed_var_with_deps(k, dep)); - } else if (m_nla_settings.grobner_subs_fixed() == 2 && var_is_fixed_to_zero(k)) { - r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(k, zero_dep)); - dep = zero_dep; - return r; - } else { - r *= m_pdd_manager.mk_var(k); - } - } - return r; -} - -void core::add_row_to_grobner(const vector> & row) { - u_dependency *dep = nullptr; - dd::pdd sum = m_pdd_manager.mk_val(rational(0)); - for (const auto &p : row) { - sum += pdd_expr(p.coeff(), p.var(), dep); - } - m_pdd_grobner.add(sum, dep); -} - - -void core::find_nl_cluster() { - prepare_rows_and_active_vars(); - svector q; - for (lpvar j : m_to_refine) { - TRACE("grobner", print_monic(emons()[j], tout) << "\n";); - q.push_back(j); - } - - while (!q.empty()) { - lpvar j = q.back(); - q.pop_back(); - add_var_and_its_factors_to_q_and_collect_new_rows(j, q); - } - TRACE("grobner", display_matrix_of_m_rows(tout);); -} - -void core::prepare_rows_and_active_vars() { - m_rows.clear(); - m_rows.resize(m_lar_solver.row_count()); - clear_and_resize_active_var_set(); -} - - -std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e ) { - auto ret = get_vars_of_expr(e); - auto & ls = m_lar_solver; - svector added; - for (auto j : ret) { - added.push_back(j); - } - for (unsigned i = 0; i < added.size(); ++i) { - lpvar j = added[i]; - if (ls.column_corresponds_to_term(j)) { - const auto& t = m_lar_solver.get_term(lp::tv::raw(ls.local_to_external(j))); - for (auto p : t) { - if (ret.find(p.column()) == ret.end()) { - added.push_back(p.column()); - ret.insert(p.column()); - } - } - } - } - return ret; -} - -void core::display_matrix_of_m_rows(std::ostream & out) const { - const auto& matrix = m_lar_solver.A_r(); - out << m_rows.size() << " rows" <<"\n"; - out << "the matrix\n"; - for (const auto & r : matrix.m_rows) { - print_row(r, out) << std::endl; - } -} - -void core::set_active_vars_weights(nex_creator& nc) { - nc.set_number_of_vars(m_lar_solver.column_count()); - for (lpvar j : active_var_set()) { - nc.set_var_weight(j, get_var_weight(j)); - } -} - -void core::set_level2var_for_grobner() { - unsigned n = m_lar_solver.column_count(); - unsigned_vector sorted_vars(n), weighted_vars(n); - for (unsigned j = 0; j < n; j++) { - sorted_vars[j] = j; - weighted_vars[j] = get_var_weight(j); - } -#if 1 - // potential update to weights - for (unsigned j = 0; j < n; j++) { - if (is_monic_var(j) && m_to_refine.contains(j)) { - for (lpvar k : m_emons[j].vars()) { - weighted_vars[k] += 6; - } - } - } -#endif - - std::sort(sorted_vars.begin(), sorted_vars.end(), [&](unsigned a, unsigned b) { - unsigned wa = weighted_vars[a]; - unsigned wb = weighted_vars[b]; - return wa < wb || (wa == wb && a < b); }); - - unsigned_vector l2v(n); - for (unsigned j = 0; j < n; j++) - l2v[j] = sorted_vars[j]; - - m_pdd_manager.reset(l2v); -} - -unsigned core::get_var_weight(lpvar j) const { - unsigned k; - switch (m_lar_solver.get_column_type(j)) { - - case lp::column_type::fixed: - k = 0; - break; - case lp::column_type::boxed: - k = 2; - break; - case lp::column_type::lower_bound: - case lp::column_type::upper_bound: - k = 4; - break; - case lp::column_type::free_column: - k = 6; - break; - default: - UNREACHABLE(); - break; - } - if (is_monic_var(j)) { - k++; - if (m_to_refine.contains(j)) { - k++; - } - } - return k; -} - -bool core::is_nl_var(lpvar j) const { - return is_monic_var(j) || m_emons.is_used_in_monic(j); -} - -bool core::influences_nl_var(lpvar j) const { - if (lp::tv::is_term(j)) - j = lp::tv::unmask_term(j); - if (is_nl_var(j)) - return true; - for (const auto & c : m_lar_solver.A_r().m_columns[j]) { - lpvar basic_in_row = m_lar_solver.r_basis()[c.var()]; - if (is_nl_var(basic_in_row)) - return true; - } - return false; -} - -void core::collect_statistics(::statistics & st) { - st.update("arith-nla-explanations", m_stats.m_nla_explanations); - st.update("arith-nla-lemmas", m_stats.m_nla_lemmas); - st.update("arith-nra-calls", m_stats.m_nra_calls); -} - - -} // end of nla - + /*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + nla_core.cpp + +Author: + Lev Nachmanson (levnach) + Nikolaj Bjorner (nbjorner) + +--*/ +#include "util/uint_set.h" +#include "math/lp/nla_core.h" +#include "math/lp/factorization_factory_imp.h" +#include "math/lp/nex.h" +#include "math/grobner/pdd_solver.h" +#include "math/dd/pdd_interval.h" +#include "math/dd/pdd_eval.h" +namespace nla { + +typedef lp::lar_term term; + +core::core(lp::lar_solver& s, reslimit & lim) : + m_evars(), + m_lar_solver(s), + m_reslim(lim), + m_tangents(this), + m_basics(this), + m_order(this), + m_monotone(this), + m_intervals(this, lim), + m_monomial_bounds(this), + m_horner(this), + m_grobner(this), + m_emons(m_evars), + m_use_nra_model(false), + m_nra(s, m_nra_lim, *this) +{ + m_nlsat_delay = lp_settings().nlsat_delay(); +} + +bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const { + switch(cmp) { + case llc::LE: return ls <= rs; + case llc::LT: return ls < rs; + case llc::GE: return ls >= rs; + case llc::GT: return ls > rs; + case llc::EQ: return ls == rs; + case llc::NE: return ls != rs; + default: SASSERT(false); + }; + + return false; +} + +rational core::value(const lp::lar_term& r) const { + rational ret(0); + for (lp::lar_term::ival t : r) + ret += t.coeff() * val(t.column()); + return ret; +} + +lp::lar_term core::subs_terms_to_columns(const lp::lar_term& t) const { + lp::lar_term r; + for (lp::lar_term::ival p : t) { + lpvar j = p.column(); + if (lp::tv::is_term(j)) + j = m_lar_solver.map_term_index_to_column_index(j); + r.add_monomial(p.coeff(), j); + } + return r; +} + +bool core::ineq_holds(const ineq& n) const { + return compare_holds(value(n.term()), n.cmp(), n.rs()); +} + +bool core::lemma_holds(const lemma& l) const { + for (const ineq &i : l.ineqs()) + if (ineq_holds(i)) + return true; + return false; +} + +lpvar core::map_to_root(lpvar j) const { + return m_evars.find(j).var(); +} + +svector core::sorted_rvars(const factor& f) const { + if (f.is_var()) { + svector r; r.push_back(map_to_root(f.var())); + return r; + } + return m_emons[f.var()].rvars(); +} + +// the value of the factor is equal to the value of the variable multiplied +// by the canonize_sign +bool core::canonize_sign(const factor& f) const { + return f.sign() ^ (f.is_var()? canonize_sign(f.var()) : canonize_sign(m_emons[f.var()])); +} + +bool core::canonize_sign(lpvar j) const { + return m_evars.find(j).sign(); +} + +bool core::canonize_sign_is_correct(const monic& m) const { + bool r = false; + for (lpvar j : m.vars()) { + r ^= canonize_sign(j); + } + return r == m.rsign(); +} + +bool core::canonize_sign(const monic& m) const { + SASSERT(canonize_sign_is_correct(m)); + return m.rsign(); +} + +bool core::canonize_sign(const factorization& f) const { + bool r = false; + for (const factor & a : f) { + r ^= canonize_sign(a); + } + return r; +} + +void core::add_monic(lpvar v, unsigned sz, lpvar const* vs) { + m_add_buffer.resize(sz); + for (unsigned i = 0; i < sz; i++) { + lpvar j = vs[i]; + if (lp::tv::is_term(j)) + j = m_lar_solver.map_term_index_to_column_index(j); + m_add_buffer[i] = j; + } + m_emons.add(v, m_add_buffer); +} + +void core::push() { + TRACE("nla_solver_verbose", tout << "\n";); + m_emons.push(); +} + + +void core::pop(unsigned n) { + TRACE("nla_solver_verbose", tout << "n = " << n << "\n";); + m_emons.pop(n); + SASSERT(elists_are_consistent(false)); +} + +rational core::product_value(const monic& m) const { + rational r(1); + for (auto j : m.vars()) { + r *= m_lar_solver.get_column_value(j).x; + } + return r; +} + +// return true iff the monic value is equal to the product of the values of the factors +bool core::check_monic(const monic& m) const { + SASSERT((!m_lar_solver.column_is_int(m.var())) || m_lar_solver.get_column_value(m.var()).is_int()); + bool ret = product_value(m) == m_lar_solver.get_column_value(m.var()).x; + CTRACE("nla_solver_check_monic", !ret, print_monic(m, tout) << '\n';); + return ret; +} + + +template +std::ostream& core::print_product(const T & m, std::ostream& out) const { + bool first = true; + for (lpvar v : m) { + if (!first) out << "*"; else first = false; + if (lp_settings().print_external_var_name()) + out << "(" << m_lar_solver.get_variable_name(v) << "=" << val(v) << ")"; + else + out << "(j" << v << " = " << val(v) << ")"; + + } + return out; +} +template +std::string core::product_indices_str(const T & m) const { + std::stringstream out; + bool first = true; + for (lpvar v : m) { + if (!first) + out << "*"; + else + first = false; + out << "j" << v;; + } + return out.str(); +} + +std::ostream & core::print_factor(const factor& f, std::ostream& out) const { + if (f.sign()) + out << "- "; + if (f.is_var()) { + out << "VAR, " << pp(f.var()); + } else { + out << "MON, v" << m_emons[f.var()] << " = "; + print_product(m_emons[f.var()].rvars(), out); + } + out << "\n"; + return out; +} + +std::ostream & core::print_factor_with_vars(const factor& f, std::ostream& out) const { + if (f.is_var()) { + out << pp(f.var()); + } + else { + out << " MON = " << pp_mon_with_vars(*this, m_emons[f.var()]); + } + return out; +} + +std::ostream& core::print_monic(const monic& m, std::ostream& out) const { + if (lp_settings().print_external_var_name()) + out << "([" << m.var() << "] = " << m_lar_solver.get_variable_name(m.var()) << " = " << val(m.var()) << " = "; + else + out << "(j" << m.var() << " = " << val(m.var()) << " = "; + print_product(m.vars(), out) << ")\n"; + return out; +} + + +std::ostream& core::print_bfc(const factorization& m, std::ostream& out) const { + SASSERT(m.size() == 2); + out << "( x = " << pp(m[0]) << "* y = " << pp(m[1]) << ")"; + return out; +} + +std::ostream& core::print_monic_with_vars(lpvar v, std::ostream& out) const { + return print_monic_with_vars(m_emons[v], out); +} +template +std::ostream& core::print_product_with_vars(const T& m, std::ostream& out) const { + print_product(m, out) << "\n"; + for (unsigned k = 0; k < m.size(); k++) { + print_var(m[k], out); + } + return out; +} + +std::ostream& core::print_monic_with_vars(const monic& m, std::ostream& out) const { + out << "[" << pp(m.var()) << "]\n"; + out << "vars:"; print_product_with_vars(m.vars(), out) << "\n"; + if (m.vars() == m.rvars()) + out << "same rvars, and m.rsign = " << m.rsign() << " of course\n"; + else { + out << "rvars:"; print_product_with_vars(m.rvars(), out) << "\n"; + out << "rsign:" << m.rsign() << "\n"; + } + return out; +} + +std::ostream& core::print_explanation(const lp::explanation& exp, std::ostream& out) const { + out << "expl: "; + unsigned i = 0; + for (auto p : exp) { + out << "(" << p.ci() << ")"; + m_lar_solver.constraints().display(out, [this](lpvar j) { return var_str(j);}, p.ci()); + if (++i < exp.size()) + out << " "; + } + return out; +} + +bool core::explain_upper_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { + rational b(0); // the bound + for (lp::lar_term::ival p : t) { + rational pb; + if (explain_coeff_upper_bound(p, pb, e)) { + b += pb; + } else { + e.clear(); + return false; + } + } + if (b > rs ) { + e.clear(); + return false; + } + return true; +} +bool core::explain_lower_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { + rational b(0); // the bound + for (lp::lar_term::ival p : t) { + rational pb; + if (explain_coeff_lower_bound(p, pb, e)) { + b += pb; + } else { + e.clear(); + return false; + } + } + if (b < rs ) { + e.clear(); + return false; + } + return true; +} + +bool core::explain_coeff_lower_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { + const rational& a = p.coeff(); + SASSERT(!a.is_zero()); + unsigned c; // the index for the lower or the upper bound + if (a.is_pos()) { + unsigned c = m_lar_solver.get_column_lower_bound_witness(p.column()); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_lower_bound(p.column()).x; + e.push_back(c); + return true; + } + // a.is_neg() + c = m_lar_solver.get_column_upper_bound_witness(p.column()); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_upper_bound(p.column()).x; + e.push_back(c); + return true; +} + +bool core::explain_coeff_upper_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { + const rational& a = p.coeff(); + lpvar j = p.column(); + SASSERT(!a.is_zero()); + unsigned c; // the index for the lower or the upper bound + if (a.is_neg()) { + unsigned c = m_lar_solver.get_column_lower_bound_witness(j); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_lower_bound(j).x; + e.push_back(c); + return true; + } + // a.is_pos() + c = m_lar_solver.get_column_upper_bound_witness(j); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_upper_bound(j).x; + e.push_back(c); + return true; +} + +// return true iff the negation of the ineq can be derived from the constraints +bool core::explain_ineq(new_lemma& lemma, const lp::lar_term& t, llc cmp, const rational& rs) { + // check that we have something like 0 < 0, which is always false and can be safely + // removed from the lemma + + if (t.is_empty() && rs.is_zero() && + (cmp == llc::LT || cmp == llc::GT || cmp == llc::NE)) return true; + lp::explanation exp; + bool r; + switch (negate(cmp)) { + case llc::LE: + r = explain_upper_bound(t, rs, exp); + break; + case llc::LT: + r = explain_upper_bound(t, rs - rational(1), exp); + break; + case llc::GE: + r = explain_lower_bound(t, rs, exp); + break; + case llc::GT: + r = explain_lower_bound(t, rs + rational(1), exp); + break; + + case llc::EQ: + r = (explain_lower_bound(t, rs, exp) && explain_upper_bound(t, rs, exp)) || + (rs.is_zero() && explain_by_equiv(t, exp)); + break; + case llc::NE: + // TBD - NB: does this work for Reals? + r = explain_lower_bound(t, rs + rational(1), exp) || explain_upper_bound(t, rs - rational(1), exp); + break; + default: + UNREACHABLE(); + return false; + } + if (r) { + lemma &= exp; + return true; + } + + return false; +} + +/** + * \brief + if t is an octagon term -+x -+ y try to explain why the term always is + equal zero +*/ +bool core::explain_by_equiv(const lp::lar_term& t, lp::explanation& e) const { + lpvar i,j; + bool sign; + if (!is_octagon_term(t, sign, i, j)) + return false; + if (m_evars.find(signed_var(i, false)) != m_evars.find(signed_var(j, sign))) + return false; + + m_evars.explain(signed_var(i, false), signed_var(j, sign), e); + TRACE("nla_solver", tout << "explained :"; m_lar_solver.print_term_as_indices(t, tout);); + return true; +} + +void core::mk_ineq_no_expl_check(new_lemma& lemma, lp::lar_term& t, llc cmp, const rational& rs) { + TRACE("nla_solver_details", m_lar_solver.print_term_as_indices(t, tout << "t = ");); + lemma |= ineq(cmp, t, rs); + CTRACE("nla_solver", ineq_holds(ineq(cmp, t, rs)), print_ineq(ineq(cmp, t, rs), tout) << "\n";); + SASSERT(!ineq_holds(ineq(cmp, t, rs))); +} + +llc apply_minus(llc cmp) { + switch(cmp) { + case llc::LE: return llc::GE; + case llc::LT: return llc::GT; + case llc::GE: return llc::LE; + case llc::GT: return llc::LT; + default: break; + } + return cmp; +} + +// the monics should be equal by modulo sign but this is not so in the model +void core::fill_explanation_and_lemma_sign(new_lemma& lemma, const monic& a, const monic & b, rational const& sign) { + SASSERT(sign == 1 || sign == -1); + lemma &= a; + lemma &= b; + TRACE("nla_solver", tout << "used constraints: " << lemma;); + SASSERT(lemma.num_ineqs() == 0); + lemma |= ineq(term(rational(1), a.var(), -sign, b.var()), llc::EQ, 0); +} + +// Replaces each variable index by the root in the tree and flips the sign if the var comes with a minus. +// Also sorts the result. +// +svector core::reduce_monic_to_rooted(const svector & vars, rational & sign) const { + svector ret; + bool s = false; + for (lpvar v : vars) { + auto root = m_evars.find(v); + s ^= root.sign(); + TRACE("nla_solver_eq", + tout << pp(v) << " mapped to " << pp(root.var()) << "\n";); + ret.push_back(root.var()); + } + sign = rational(s? -1: 1); + std::sort(ret.begin(), ret.end()); + return ret; +} + + +// Replaces definition m_v = v1* .. * vn by +// m_v = coeff * w1 * ... * wn, where w1, .., wn are canonical +// representatives, which are the roots of the equivalence tree, under current equations. +// +monic_coeff core::canonize_monic(monic const& m) const { + rational sign = rational(1); + svector vars = reduce_monic_to_rooted(m.vars(), sign); + return monic_coeff(vars, sign); +} + +int core::vars_sign(const svector& v) { + int sign = 1; + for (lpvar j : v) { + sign *= nla::rat_sign(val(j)); + if (sign == 0) + return 0; + } + return sign; +} + +bool core::has_upper_bound(lpvar j) const { + return m_lar_solver.column_has_upper_bound(j); +} + +bool core::has_lower_bound(lpvar j) const { + return m_lar_solver.column_has_lower_bound(j); +} +const rational& core::get_upper_bound(unsigned j) const { + return m_lar_solver.get_upper_bound(j).x; +} + +const rational& core::get_lower_bound(unsigned j) const { + return m_lar_solver.get_lower_bound(j).x; +} + +bool core::zero_is_an_inner_point_of_bounds(lpvar j) const { + if (has_upper_bound(j) && get_upper_bound(j) <= rational(0)) + return false; + if (has_lower_bound(j) && get_lower_bound(j) >= rational(0)) + return false; + return true; +} + +int core::rat_sign(const monic& m) const { + int sign = 1; + for (lpvar j : m.vars()) { + auto v = val(j); + if (v.is_neg()) { + sign = - sign; + continue; + } + if (v.is_pos()) { + continue; + } + sign = 0; + break; + } + return sign; +} + +// Returns true if the monic sign is incorrect +bool core::sign_contradiction(const monic& m) const { + return nla::rat_sign(var_val(m)) != rat_sign(m); +} + +/* + unsigned_vector eq_vars(lpvar j) const { + TRACE("nla_solver_eq", tout << "j = " << pp(j) << "eqs = "; + for(auto jj : m_evars.eq_vars(j)) tout << pp(jj) << " "; + }); + return m_evars.eq_vars(j); + } +*/ + +bool core::var_is_fixed_to_zero(lpvar j) const { + return + m_lar_solver.column_is_fixed(j) && + m_lar_solver.get_lower_bound(j) == lp::zero_of_type(); +} +bool core::var_is_fixed_to_val(lpvar j, const rational& v) const { + return + m_lar_solver.column_is_fixed(j) && + m_lar_solver.get_lower_bound(j) == lp::impq(v); +} + +bool core::var_is_fixed(lpvar j) const { + return m_lar_solver.column_is_fixed(j); +} + +bool core::var_is_free(lpvar j) const { + return m_lar_solver.column_is_free(j); +} + + +std::ostream & core::print_ineq(const ineq & in, std::ostream & out) const { + m_lar_solver.print_term_as_indices(in.term(), out); + out << " " << lconstraint_kind_string(in.cmp()) << " " << in.rs(); + return out; +} + +std::ostream & core::print_var(lpvar j, std::ostream & out) const { + if (is_monic_var(j)) { + print_monic(m_emons[j], out); + } + + m_lar_solver.print_column_info(j, out); + signed_var jr = m_evars.find(j); + out << "root="; + if (jr.sign()) { + out << "-"; + } + + out << m_lar_solver.get_variable_name(jr.var()) << "\n"; + return out; +} + +std::ostream & core::print_monics(std::ostream & out) const { + for (auto &m : m_emons) { + print_monic_with_vars(m, out); + } + return out; +} + +std::ostream & core::print_ineqs(const lemma& l, std::ostream & out) const { + std::unordered_set vars; + out << "ineqs: "; + if (l.ineqs().size() == 0) { + out << "conflict\n"; + } else { + for (unsigned i = 0; i < l.ineqs().size(); i++) { + auto & in = l.ineqs()[i]; + print_ineq(in, out); + if (i + 1 < l.ineqs().size()) out << " or "; + for (lp::lar_term::ival p: in.term()) + vars.insert(p.column()); + } + out << std::endl; + for (lpvar j : vars) { + print_var(j, out); + } + out << "\n"; + } + return out; +} + +std::ostream & core::print_factorization(const factorization& f, std::ostream& out) const { + if (f.is_mon()){ + out << "is_mon " << pp_mon(*this, f.mon()); + } + else { + for (unsigned k = 0; k < f.size(); k++ ) { + out << "(" << pp(f[k]) << ")"; + if (k < f.size() - 1) + out << "*"; + } + } + return out; +} + +bool core::find_canonical_monic_of_vars(const svector& vars, lpvar & i) const { + monic const* sv = m_emons.find_canonical(vars); + return sv && (i = sv->var(), true); +} + +bool core::is_canonical_monic(lpvar j) const { + return m_emons.is_canonical_monic(j); +} + + +void core::trace_print_monic_and_factorization(const monic& rm, const factorization& f, std::ostream& out) const { + out << "rooted vars: "; + print_product(rm.rvars(), out) << "\n"; + out << "mon: " << pp_mon(*this, rm.var()) << "\n"; + out << "value: " << var_val(rm) << "\n"; + print_factorization(f, out << "fact: ") << "\n"; +} + + +bool core::var_has_positive_lower_bound(lpvar j) const { + return m_lar_solver.column_has_lower_bound(j) && m_lar_solver.get_lower_bound(j) > lp::zero_of_type(); +} + +bool core::var_has_negative_upper_bound(lpvar j) const { + return m_lar_solver.column_has_upper_bound(j) && m_lar_solver.get_upper_bound(j) < lp::zero_of_type(); +} + +bool core::var_is_separated_from_zero(lpvar j) const { + return + var_has_negative_upper_bound(j) || + var_has_positive_lower_bound(j); +} + + +bool core::vars_are_equiv(lpvar a, lpvar b) const { + SASSERT(abs(val(a)) == abs(val(b))); + return m_evars.vars_are_equiv(a, b); +} + +bool core::has_zero_factor(const factorization& factorization) const { + for (factor f : factorization) { + if (val(f).is_zero()) + return true; + } + return false; +} + + +template +bool core::mon_has_zero(const T& product) const { + for (lpvar j: product) { + if (val(j).is_zero()) + return true; + } + return false; +} + +template bool core::mon_has_zero(const unsigned_vector& product) const; + + +lp::lp_settings& core::lp_settings() { + return m_lar_solver.settings(); +} +const lp::lp_settings& core::lp_settings() const { + return m_lar_solver.settings(); +} + +unsigned core::random() { return lp_settings().random_next(); } + + +// we look for octagon constraints here, with a left part +-x +- y +void core::collect_equivs() { + const lp::lar_solver& s = m_lar_solver; + + for (unsigned i = 0; i < s.terms().size(); i++) { + if (!s.term_is_used_as_row(i)) + continue; + lpvar j = s.external_to_local(lp::tv::mask_term(i)); + if (var_is_fixed_to_zero(j)) { + TRACE("nla_solver_mons", s.print_term_as_indices(*s.terms()[i], tout << "term = ") << "\n";); + add_equivalence_maybe(s.terms()[i], s.get_column_upper_bound_witness(j), s.get_column_lower_bound_witness(j)); + } + } + m_emons.ensure_canonized(); +} + + +// returns true iff the term is in a form +-x-+y. +// the sign is true iff the term is x+y, -x-y. +bool core::is_octagon_term(const lp::lar_term& t, bool & sign, lpvar& i, lpvar &j) const { + if (t.size() != 2) + return false; + bool seen_minus = false; + bool seen_plus = false; + i = null_lpvar; + for(lp::lar_term::ival p : t) { + const auto & c = p.coeff(); + if (c == 1) { + seen_plus = true; + } else if (c == - 1) { + seen_minus = true; + } else { + return false; + } + if (i == null_lpvar) + i = p.column(); + else + j = p.column(); + } + SASSERT(j != null_lpvar); + sign = (seen_minus && seen_plus)? false : true; + return true; +} + +void core::add_equivalence_maybe(const lp::lar_term *t, lpci c0, lpci c1) { + bool sign; + lpvar i, j; + if (!is_octagon_term(*t, sign, i, j)) + return; + if (sign) + m_evars.merge_minus(i, j, eq_justification({c0, c1})); + else + m_evars.merge_plus(i, j, eq_justification({c0, c1})); +} + +// x is equivalent to y if x = +- y +void core::init_vars_equivalence() { + collect_equivs(); + // SASSERT(tables_are_ok()); +} + +bool core::vars_table_is_ok() const { + // return m_var_eqs.is_ok(); + return true; +} + +bool core::rm_table_is_ok() const { + // return m_emons.is_ok(); + return true; +} + +bool core::tables_are_ok() const { + return vars_table_is_ok() && rm_table_is_ok(); +} + +bool core::var_is_a_root(lpvar j) const { return m_evars.is_root(j); } + +template +bool core::vars_are_roots(const T& v) const { + for (lpvar j: v) { + if (!var_is_a_root(j)) + return false; + } + return true; +} + + + +template +void core::trace_print_rms(const T& p, std::ostream& out) { + out << "p = {\n"; + for (auto j : p) { + out << "j = " << j << ", rm = " << m_emons[j] << "\n"; + } + out << "}"; +} + +void core::print_monic_stats(const monic& m, std::ostream& out) { + if (m.size() == 2) return; + monic_coeff mc = canonize_monic(m); + for(unsigned i = 0; i < mc.vars().size(); i++){ + if (abs(val(mc.vars()[i])) == rational(1)) { + auto vv = mc.vars(); + vv.erase(vv.begin()+i); + monic const* sv = m_emons.find_canonical(vv); + if (!sv) { + out << "nf length" << vv.size() << "\n"; ; + } + } + } +} + +void core::print_stats(std::ostream& out) { +} + + +void core::clear() { + m_lemma_vec->clear(); +} + +void core::init_search() { + TRACE("nla_solver_mons", tout << "init\n";); + SASSERT(m_emons.invariant()); + clear(); + init_vars_equivalence(); + SASSERT(m_emons.invariant()); + SASSERT(elists_are_consistent(false)); +} + +void core::insert_to_refine(lpvar j) { + TRACE("lar_solver", tout << "j=" << j << '\n';); + m_to_refine.insert(j); +} + +void core::erase_from_to_refine(lpvar j) { + TRACE("lar_solver", tout << "j=" << j << '\n';); + m_to_refine.erase(j); +} + + +void core::init_to_refine() { + TRACE("nla_solver_details", tout << "emons:" << pp_emons(*this, m_emons);); + m_to_refine.clear(); + m_to_refine.resize(m_lar_solver.number_of_vars()); + unsigned r = random(), sz = m_emons.number_of_monics(); + for (unsigned k = 0; k < sz; k++) { + auto const & m = *(m_emons.begin() + (k + r)% sz); + if (!check_monic(m)) + insert_to_refine(m.var()); + } + + TRACE("nla_solver", + tout << m_to_refine.size() << " mons to refine:\n"; + for (lpvar v : m_to_refine) tout << pp_mon(*this, m_emons[v]) << ":error = " << + (val(v) - mul_val(m_emons[v])).get_double() << "\n";); +} + +std::unordered_set core::collect_vars(const lemma& l) const { + std::unordered_set vars; + auto insert_j = [&](lpvar j) { + vars.insert(j); + if (is_monic_var(j)) { + for (lpvar k : m_emons[j].vars()) + vars.insert(k); + } + }; + + for (const auto& i : l.ineqs()) { + for (lp::lar_term::ival p : i.term()) { + insert_j(p.column()); + } + } + for (auto p : l.expl()) { + const auto& c = m_lar_solver.constraints()[p.ci()]; + for (const auto& r : c.coeffs()) { + insert_j(r.second); + } + } + return vars; +} + +// divides bc by c, so bc = b*c +bool core::divide(const monic& bc, const factor& c, factor & b) const { + svector c_rvars = sorted_rvars(c); + TRACE("nla_solver_div", tout << "c_rvars = "; print_product(c_rvars, tout); tout << "\nbc_rvars = "; print_product(bc.rvars(), tout);); + if (!lp::is_proper_factor(c_rvars, bc.rvars())) + return false; + + auto b_rvars = lp::vector_div(bc.rvars(), c_rvars); + TRACE("nla_solver_div", tout << "b_rvars = "; print_product(b_rvars, tout);); + SASSERT(b_rvars.size() > 0); + if (b_rvars.size() == 1) { + b = factor(b_rvars[0], factor_type::VAR); + } else { + monic const* sv = m_emons.find_canonical(b_rvars); + if (sv == nullptr) { + TRACE("nla_solver_div", tout << "not in rooted";); + return false; + } + b = factor(sv->var(), factor_type::MON); + } + SASSERT(!b.sign()); + // We have bc = canonize_sign(bc)*bc.rvars() = canonize_sign(b)*b.rvars()*canonize_sign(c)*c.rvars(). + // Dividing by bc.rvars() we get canonize_sign(bc) = canonize_sign(b)*canonize_sign(c) + // Currently, canonize_sign(b) is 1, we might need to adjust it + b.sign() = canonize_sign(b) ^ canonize_sign(c) ^ canonize_sign(bc); + TRACE("nla_solver", tout << "success div:" << pp(b) << "\n";); + return true; +} + + +void core::negate_factor_equality(new_lemma& lemma, const factor& c, + const factor& d) { + if (c == d) + return; + lpvar i = var(c); + lpvar j = var(d); + auto iv = val(i), jv = val(j); + SASSERT(abs(iv) == abs(jv)); + lemma |= ineq(term(i, rational(iv == jv ? -1 : 1), j), llc::NE, 0); +} + +void core::negate_factor_relation(new_lemma& lemma, const rational& a_sign, const factor& a, const rational& b_sign, const factor& b) { + rational a_fs = sign_to_rat(canonize_sign(a)); + rational b_fs = sign_to_rat(canonize_sign(b)); + llc cmp = a_sign*val(a) < b_sign*val(b)? llc::GE : llc::LE; + lemma |= ineq(term(a_fs*a_sign, var(a), - b_fs*b_sign, var(b)), cmp, 0); +} + +std::ostream& core::print_lemma(const lemma& l, std::ostream& out) const { + static int n = 0; + out << "lemma:" << ++n << " "; + print_ineqs(l, out); + print_explanation(l.expl(), out); + for (lpvar j : collect_vars(l)) { + print_var(j, out); + } + return out; +} + + +void core::trace_print_ol(const monic& ac, + const factor& a, + const factor& c, + const monic& bc, + const factor& b, + std::ostream& out) { + out << "ac = " << pp_mon(*this, ac) << "\n"; + out << "bc = " << pp_mon(*this, bc) << "\n"; + out << "a = "; + print_factor_with_vars(a, out); + out << ", \nb = "; + print_factor_with_vars(b, out); + out << "\nc = "; + print_factor_with_vars(c, out); +} + +void core::maybe_add_a_factor(lpvar i, + const factor& c, + std::unordered_set& found_vars, + std::unordered_set& found_rm, + vector & r) const { + SASSERT(abs(val(i)) == abs(val(c))); + if (!is_monic_var(i)) { + i = m_evars.find(i).var(); + if (try_insert(i, found_vars)) { + r.push_back(factor(i, factor_type::VAR)); + } + } else { + if (try_insert(i, found_rm)) { + r.push_back(factor(i, factor_type::MON)); + TRACE("nla_solver", tout << "inserting factor = "; print_factor_with_vars(factor(i, factor_type::MON), tout); ); + } + } +} + + +// Returns rooted monics by arity +std::unordered_map core::get_rm_by_arity() { + std::unordered_map m; + for (auto const& mon : m_emons) { + unsigned arity = mon.vars().size(); + auto it = m.find(arity); + if (it == m.end()) { + it = m.insert(it, std::make_pair(arity, unsigned_vector())); + } + it->second.push_back(mon.var()); + } + return m; +} + +bool core::rm_check(const monic& rm) const { + return check_monic(m_emons[rm.var()]); +} + + +bool core::find_bfc_to_refine_on_monic(const monic& m, factorization & bf) { + for (auto f : factorization_factory_imp(m, *this)) { + if (f.size() == 2) { + auto a = f[0]; + auto b = f[1]; + if (var_val(m) != val(a) * val(b)) { + bf = f; + TRACE("nla_solver", tout << "found bf"; + tout << ":m:" << pp_mon_with_vars(*this, m) << "\n"; + tout << "bf:"; print_bfc(bf, tout);); + + return true; + } + } + } + return false; +} + +// finds a monic to refine with its binary factorization +bool core::find_bfc_to_refine(const monic* & m, factorization & bf){ + m = nullptr; + unsigned r = random(), sz = m_to_refine.size(); + for (unsigned k = 0; k < sz; k++) { + lpvar i = m_to_refine[(k + r) % sz]; + m = &m_emons[i]; + SASSERT (!check_monic(*m)); + if (has_real(m)) + continue; + if (m->size() == 2) { + bf.set_mon(m); + bf.push_back(factor(m->vars()[0], factor_type::VAR)); + bf.push_back(factor(m->vars()[1], factor_type::VAR)); + return true; + } + + if (find_bfc_to_refine_on_monic(*m, bf)) { + TRACE("nla_solver", + tout << "bf = "; print_factorization(bf, tout); + tout << "\nval(*m) = " << var_val(*m) << ", should be = (val(bf[0])=" << val(bf[0]) << ")*(val(bf[1]) = " << val(bf[1]) << ") = " << val(bf[0])*val(bf[1]) << "\n";); + return true; + } + } + return false; +} + +rational core::val(const factorization& f) const { + rational r(1); + for (const factor &p : f) { + r *= val(p); + } + return r; +} + +new_lemma::new_lemma(core& c, char const* name):name(name), c(c) { + c.m_lemma_vec->push_back(lemma()); +} + +new_lemma& new_lemma::operator|=(ineq const& ineq) { + if (!c.explain_ineq(*this, ineq.term(), ineq.cmp(), ineq.rs())) { + CTRACE("nla_solver", c.ineq_holds(ineq), c.print_ineq(ineq, tout) << "\n";); + SASSERT(!c.ineq_holds(ineq)); + current().push_back(ineq); + } + return *this; +} + + +new_lemma::~new_lemma() { + static int i = 0; + (void)i; + (void)name; + // code for checking lemma can be added here + TRACE("nla_solver", tout << name << " " << (++i) << "\n" << *this; ); +} + +lemma& new_lemma::current() const { + return c.m_lemma_vec->back(); +} + +new_lemma& new_lemma::operator&=(lp::explanation const& e) { + expl().add_expl(e); + return *this; +} + +new_lemma& new_lemma::operator&=(const monic& m) { + for (lpvar j : m.vars()) + *this &= j; + return *this; +} + +new_lemma& new_lemma::operator&=(const factor& f) { + if (f.type() == factor_type::VAR) + *this &= f.var(); + else + *this &= c.m_emons[f.var()]; + return *this; +} + +new_lemma& new_lemma::operator&=(const factorization& f) { + if (f.is_mon()) + return *this; + for (const auto& fc : f) { + *this &= fc; + } + return *this; +} + +new_lemma& new_lemma::operator&=(lpvar j) { + c.m_evars.explain(j, expl()); + return *this; +} + +new_lemma& new_lemma::explain_fixed(lpvar j) { + SASSERT(c.var_is_fixed(j)); + explain_existing_lower_bound(j); + explain_existing_upper_bound(j); + return *this; +} + +new_lemma& new_lemma::explain_equiv(lpvar a, lpvar b) { + SASSERT(abs(c.val(a)) == abs(c.val(b))); + if (c.vars_are_equiv(a, b)) { + *this &= a; + *this &= b; + } else { + explain_fixed(a); + explain_fixed(b); + } + return *this; +} + +new_lemma& new_lemma::explain_var_separated_from_zero(lpvar j) { + SASSERT(c.var_is_separated_from_zero(j)); + if (c.m_lar_solver.column_has_upper_bound(j) && + (c.m_lar_solver.get_upper_bound(j)< lp::zero_of_type())) + explain_existing_upper_bound(j); + else + explain_existing_lower_bound(j); + return *this; +} + +new_lemma& new_lemma::explain_existing_lower_bound(lpvar j) { + SASSERT(c.has_lower_bound(j)); + lp::explanation ex; + ex.push_back(c.m_lar_solver.get_column_lower_bound_witness(j)); + *this &= ex; + TRACE("nla_solver", tout << j << ": " << *this << "\n";); + return *this; +} + +new_lemma& new_lemma::explain_existing_upper_bound(lpvar j) { + SASSERT(c.has_upper_bound(j)); + lp::explanation ex; + ex.push_back(c.m_lar_solver.get_column_upper_bound_witness(j)); + *this &= ex; + return *this; +} + +std::ostream& new_lemma::display(std::ostream & out) const { + auto const& lemma = current(); + + for (auto p : lemma.expl()) { + out << "(" << p.ci() << ") "; + c.m_lar_solver.constraints().display(out, [this](lpvar j) { return c.var_str(j);}, p.ci()); + } + out << " ==> "; + if (lemma.ineqs().empty()) { + out << "false"; + } + else { + bool first = true; + for (auto & in : lemma.ineqs()) { + if (first) first = false; else out << " or "; + c.print_ineq(in, out); + } + } + out << "\n"; + for (lpvar j : c.collect_vars(lemma)) { + c.print_var(j, out); + } + return out; +} + +void core::negate_relation(new_lemma& lemma, unsigned j, const rational& a) { + SASSERT(val(j) != a); + lemma |= ineq(j, val(j) < a ? llc::GE : llc::LE, a); +} + +bool core::conflict_found() const { + for (const auto & l : * m_lemma_vec) { + if (l.is_conflict()) + return true; + } + return false; +} + +bool core::done() const { + return m_lemma_vec->size() >= 10 || + conflict_found() || + lp_settings().get_cancel_flag(); +} + +bool core::elist_is_consistent(const std::unordered_set & list) const { + bool first = true; + bool p; + for (lpvar j : list) { + if (first) { + p = check_monic(m_emons[j]); + first = false; + } else + if (check_monic(m_emons[j]) != p) + return false; + } + return true; +} + +bool core::elists_are_consistent(bool check_in_model) const { + std::unordered_map, hash_svector> lists; + if (!m_emons.elists_are_consistent(lists)) + return false; + + if (!check_in_model) + return true; + for (const auto & p : lists) { + if (! elist_is_consistent(p.second)) + return false; + } + return true; +} + +bool core::var_breaks_correct_monic_as_factor(lpvar j, const monic& m) const { + if (!val(var(m)).is_zero()) + return true; + + if (!val(j).is_zero()) // j was not zero: the new value does not matter - m must have another zero factor + return false; + // do we have another zero in m? + for (lpvar k : m) { + if (k != j && val(k).is_zero()) { + return false; // not breaking + } + } + // j was the only zero in m + return true; +} + +bool core::var_breaks_correct_monic(lpvar j) const { + if (is_monic_var(j) && !m_to_refine.contains(j)) { + TRACE("nla_solver", tout << "j = " << j << ", m = "; print_monic(emons()[j], tout) << "\n";); + return true; // changing the value of a correct monic + } + + for (const monic & m : emons().get_use_list(j)) { + if (m_to_refine.contains(m.var())) + continue; + if (var_breaks_correct_monic_as_factor(j, m)) + return true; + } + + return false; +} + +void core::update_to_refine_of_var(lpvar j) { + for (const monic & m : emons().get_use_list(j)) { + if (var_val(m) == mul_val(m)) + erase_from_to_refine(var(m)); + else + insert_to_refine(var(m)); + } + if (is_monic_var(j)) { + const monic& m = emons()[j]; + if (var_val(m) == mul_val(m)) + erase_from_to_refine(j); + else + insert_to_refine(j); + } +} + +bool core::var_is_big(lpvar j) const { + return !var_is_int(j) && val(j).is_big(); +} + +bool core::has_big_num(const monic& m) const { + if (var_is_big(var(m))) + return true; + for (lpvar j : m.vars()) + if (var_is_big(j)) + return true; + return false; +} + +bool core::has_real(const factorization& f) const { + for (const factor& fc: f) { + lpvar j = var(fc); + if (!var_is_int(j)) + return true; + } + return false; +} + +bool core::has_real(const monic& m) const { + for (lpvar j : m.vars()) + if (!var_is_int(j)) + return true; + return false; +} + +// returns true if the patching is blocking +bool core::is_patch_blocked(lpvar u, const lp::impq& ival) const { + TRACE("nla_solver", tout << "u = " << u << '\n';); + if (m_cautious_patching && + (!m_lar_solver.inside_bounds(u, ival) || (var_is_int(u) && ival.is_int() == false))) { + TRACE("nla_solver", tout << "u = " << u << " blocked, for feas or integr\n";); + return true; // block + } + + if (u == m_patched_var) { + TRACE("nla_solver", tout << "u == m_patched_var, no block\n";); + + return false; // do not block + } + // we can change only one variable in variables of m_patched_var + if (m_patched_monic->contains_var(u) || u == var(*m_patched_monic)) { + TRACE("nla_solver", tout << "u = " << u << " blocked as contained\n";); + return true; // block + } + + if (var_breaks_correct_monic(u)) { + TRACE("nla_solver", tout << "u = " << u << " blocked as used in a correct monomial\n";); + return true; + } + + TRACE("nla_solver", tout << "u = " << u << ", m_patched_m = "; print_monic(*m_patched_monic, tout) << + ", not blocked\n";); + + return false; +} + +// it tries to patch m_patched_var +bool core::try_to_patch(const rational& v) { + auto is_blocked = [this](lpvar u, const lp::impq& iv) { return is_patch_blocked(u, iv); }; + auto change_report = [this](lpvar u) { update_to_refine_of_var(u); }; + return m_lar_solver.try_to_patch(m_patched_var, v, is_blocked, change_report); +} + +bool in_power(const svector& vs, unsigned l) { + unsigned k = vs[l]; + return (l != 0 && vs[l - 1] == k) || (l + 1 < vs.size() && k == vs[l + 1]); +} + +bool core::to_refine_is_correct() const { + for (unsigned j = 0; j < m_lar_solver.number_of_vars(); j++) { + if (!is_monic_var(j)) continue; + bool valid = check_monic(emons()[j]); + if (valid == m_to_refine.contains(j)) { + TRACE("nla_solver", tout << "inconstency in m_to_refine : "; + print_monic(emons()[j], tout) << "\n"; + if (valid) tout << "should NOT be in to_refine\n"; + else tout << "should be in to_refine\n";); + return false; + } + } + return true; +} + +void core::patch_monomial(lpvar j) { + m_patched_monic =& (emons()[j]); + m_patched_var = j; + TRACE("nla_solver", tout << "m = "; print_monic(*m_patched_monic, tout) << "\n";); + rational v = mul_val(*m_patched_monic); + if (val(j) == v) { + erase_from_to_refine(j); + return; + } + if (!var_breaks_correct_monic(j) && try_to_patch(v)) { + SASSERT(to_refine_is_correct()); + return; + } + + // We could not patch j, now we try patching the factor variables. + TRACE("nla_solver", tout << " trying squares\n";); + // handle perfect squares + if ((*m_patched_monic).vars().size() == 2 && (*m_patched_monic).vars()[0] == (*m_patched_monic).vars()[1]) { + rational root; + if (v.is_perfect_square(root)) { + m_patched_var = (*m_patched_monic).vars()[0]; + if (!var_breaks_correct_monic(m_patched_var) && (try_to_patch(root) || try_to_patch(-root))) { + TRACE("nla_solver", tout << "patched square\n";); + return; + } + } + TRACE("nla_solver", tout << " cannot patch\n";); + return; + } + + // We have v != abc, but we need to have v = abc. + // If we patch b then b should be equal to v/ac = v/(abc/b) = b(v/abc) + if (!v.is_zero()) { + rational r = val(j) / v; + SASSERT((*m_patched_monic).is_sorted()); + TRACE("nla_solver", tout << "r = " << r << ", v = " << v << "\n";); + for (unsigned l = 0; l < (*m_patched_monic).size(); l++) { + m_patched_var = (*m_patched_monic).vars()[l]; + if (!in_power((*m_patched_monic).vars(), l) && + !var_breaks_correct_monic(m_patched_var) && + try_to_patch(r * val(m_patched_var))) { // r * val(k) gives the right value of k + TRACE("nla_solver", tout << "patched " << m_patched_var << "\n";); + SASSERT(mul_val((*m_patched_monic)) == val(j)); + erase_from_to_refine(j); + break; + } + } + } +} + +void core::patch_monomials_on_to_refine() { + auto to_refine = m_to_refine.index(); + // the rest of the function might change m_to_refine, so have to copy + unsigned sz = to_refine.size(); + + unsigned start = random(); + for (unsigned i = 0; i < sz; i++) { + patch_monomial(to_refine[(start + i) % sz]); + if (m_to_refine.size() == 0) + break; + } + TRACE("nla_solver", tout << "sz = " << sz << ", m_to_refine = " << m_to_refine.size() << + (sz > m_to_refine.size()? " less" : "same" ) << "\n";); +} + +void core::patch_monomials() { + m_cautious_patching = true; + patch_monomials_on_to_refine(); + if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching) { + return; + } + NOT_IMPLEMENTED_YET(); + m_cautious_patching = false; + patch_monomials_on_to_refine(); + m_lar_solver.push(); + save_tableau(); + constrain_nl_in_tableau(); + if (solve_tableau() && integrality_holds()) { + m_lar_solver.pop(1); + } else { + m_lar_solver.pop(); + restore_tableau(); + m_lar_solver.clear_inf_set(); + } + SASSERT(m_lar_solver.ax_is_correct()); +} + +void core::constrain_nl_in_tableau() { + NOT_IMPLEMENTED_YET(); +} + +bool core::solve_tableau() { + NOT_IMPLEMENTED_YET(); + return false; +} + +void core::restore_tableau() { + NOT_IMPLEMENTED_YET(); +} + +void core::save_tableau() { + NOT_IMPLEMENTED_YET(); +} + +bool core::integrality_holds() { + NOT_IMPLEMENTED_YET(); + return false; +} + +/** + * Cycle through different end-game solvers weighted by probability. + */ +void core::check_weighted(unsigned sz, std::pair>* checks) { + unsigned bound = 0; + for (unsigned i = 0; i < sz; ++i) + bound += checks[i].first; + uint_set seen; + while (bound > 0 && !done() && m_lemma_vec->empty()) { + unsigned n = random() % bound; + for (unsigned i = 0; i < sz; ++i) { + if (seen.contains(i)) + continue; + if (n < checks[i].first) { + seen.insert(i); + checks[i].second(); + bound -= checks[i].first; + break; + } + n -= checks[i].first; + } + } +} + + +lbool core::check(vector& l_vec) { + lp_settings().stats().m_nla_calls++; + TRACE("nla_solver", tout << "calls = " << lp_settings().stats().m_nla_calls << "\n";); + m_lar_solver.get_rid_of_inf_eps(); + m_lemma_vec = &l_vec; + if (!(m_lar_solver.get_status() == lp::lp_status::OPTIMAL || + m_lar_solver.get_status() == lp::lp_status::FEASIBLE)) { + TRACE("nla_solver", tout << "unknown because of the m_lar_solver.m_status = " << m_lar_solver.get_status() << "\n";); + return l_undef; + } + + init_to_refine(); + patch_monomials(); + set_use_nra_model(false); + if (m_to_refine.empty()) { return l_true; } + init_search(); + + lbool ret = l_undef; + bool run_grobner = need_run_grobner(); + bool run_horner = need_run_horner(); + bool run_bounded_nlsat = should_run_bounded_nlsat(); + + if (l_vec.empty() && !done()) + m_monomial_bounds(); + + if (l_vec.empty() && !done() && run_horner) + m_horner.horner_lemmas(); + + if (l_vec.empty() && !done() && run_grobner) + m_grobner(); + + if (l_vec.empty() && !done()) + m_basics.basic_lemma(true); + + if (l_vec.empty() && !done()) + m_basics.basic_lemma(false); + +#if 0 + if (l_vec.empty() && !done() && !run_horner) + m_horner.horner_lemmas(); + + if (l_vec.empty() && !done() && !run_grobner) + m_grobner(); +#endif + + if (!conflict_found() && !done() && run_bounded_nlsat) + ret = bounded_nlsat(); + + if (l_vec.empty() && !done() && ret == l_undef) { + std::function check1 = [&]() { m_order.order_lemma(); }; + std::function check2 = [&]() { m_monotone.monotonicity_lemma(); }; + std::function check3 = [&]() { m_tangents.tangent_lemma(); }; + + std::pair> checks[] = + { { 6, check1 }, + { 2, check2 }, + { 1, check3 }}; + check_weighted(3, checks); + + unsigned num_calls = lp_settings().stats().m_nla_calls; + if (!conflict_found() && m_nla_settings.run_nra && num_calls % 50 == 0 && num_calls > 500) + ret = bounded_nlsat(); + } + + if (l_vec.empty() && !done() && m_nla_settings.run_nra && ret == l_undef) { + ret = m_nra.check(); + m_stats.m_nra_calls++; + } + + if (ret == l_undef && !l_vec.empty() && m_reslim.inc()) + ret = l_false; + + m_stats.m_nla_lemmas += l_vec.size(); + for (const auto& l : l_vec) + m_stats.m_nla_explanations += static_cast(l.expl().size()); + + + TRACE("nla_solver", tout << "ret = " << ret << ", lemmas count = " << l_vec.size() << "\n";); + IF_VERBOSE(2, if(ret == l_undef) {verbose_stream() << "Monomials\n"; print_monics(verbose_stream());}); + CTRACE("nla_solver", ret == l_undef, tout << "Monomials\n"; print_monics(tout);); + return ret; +} + +bool core::should_run_bounded_nlsat() { + if (!m_nla_settings.run_nra) + return false; + if (m_nlsat_delay > m_nlsat_fails) + ++m_nlsat_fails; + return m_nlsat_delay <= m_nlsat_fails; +} + +lbool core::bounded_nlsat() { + params_ref p; + lbool ret; + p.set_uint("max_conflicts", 100); + m_nra.updt_params(p); + { + scoped_limits sl(m_reslim); + sl.push_child(&m_nra_lim); + scoped_rlimit sr(m_nra_lim, 100000); + ret = m_nra.check(); + } + p.set_uint("max_conflicts", UINT_MAX); + m_nra.updt_params(p); + m_stats.m_nra_calls++; + if (ret == l_undef) + ++m_nlsat_delay; + else { + m_nlsat_fails = 0; + m_nlsat_delay /= 2; + } + if (ret == l_true) { + m_lemma_vec->reset(); + } + return ret; +} + +bool core::no_lemmas_hold() const { + for (auto & l : * m_lemma_vec) { + if (lemma_holds(l)) { + TRACE("nla_solver", print_lemma(l, tout);); + return false; + } + } + return true; +} + +lbool core::test_check(vector& l) { + m_lar_solver.set_status(lp::lp_status::OPTIMAL); + return check(l); +} + +std::ostream& core::print_terms(std::ostream& out) const { + for (unsigned i = 0; i< m_lar_solver.terms().size(); i++) { + unsigned ext = lp::tv::mask_term(i); + if (!m_lar_solver.var_is_registered(ext)) { + out << "term is not registered\n"; + continue; + } + + const lp::lar_term & t = *m_lar_solver.terms()[i]; + out << "term:"; print_term(t, out) << std::endl; + lpvar j = m_lar_solver.external_to_local(ext); + print_var(j, out); + } + return out; +} + +std::string core::var_str(lpvar j) const { + std::string result; + if (is_monic_var(j)) + result += product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_"); + else + result += std::string("j") + lp::T_to_string(j); + // result += ":w" + lp::T_to_string(get_var_weight(j)); + return result; +} + +std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const { + return lp::print_linear_combination_customized( + t.coeffs_as_vector(), + [this](lpvar j) { return var_str(j); }, + out); +} + + + + +std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e ) { + auto ret = get_vars_of_expr(e); + auto & ls = m_lar_solver; + svector added; + for (auto j : ret) { + added.push_back(j); + } + for (unsigned i = 0; i < added.size(); ++i) { + lpvar j = added[i]; + if (ls.column_corresponds_to_term(j)) { + const auto& t = m_lar_solver.get_term(lp::tv::raw(ls.local_to_external(j))); + for (auto p : t) { + if (ret.find(p.column()) == ret.end()) { + added.push_back(p.column()); + ret.insert(p.column()); + } + } + } + } + return ret; +} + + +bool core::is_nl_var(lpvar j) const { + return is_monic_var(j) || m_emons.is_used_in_monic(j); +} + + +unsigned core::get_var_weight(lpvar j) const { + unsigned k; + switch (m_lar_solver.get_column_type(j)) { + + case lp::column_type::fixed: + k = 0; + break; + case lp::column_type::boxed: + k = 3; + break; + case lp::column_type::lower_bound: + case lp::column_type::upper_bound: + k = 6; + break; + case lp::column_type::free_column: + k = 9; + break; + default: + UNREACHABLE(); + break; + } + if (is_monic_var(j)) { + k++; + if (m_to_refine.contains(j)) + k++; + } + return k; +} + + +void core::set_active_vars_weights(nex_creator& nc) { + nc.set_number_of_vars(m_lar_solver.column_count()); + for (lpvar j : active_var_set()) + nc.set_var_weight(j, get_var_weight(j)); +} + +bool core::influences_nl_var(lpvar j) const { + if (lp::tv::is_term(j)) + j = lp::tv::unmask_term(j); + if (is_nl_var(j)) + return true; + for (const auto & c : m_lar_solver.A_r().m_columns[j]) { + lpvar basic_in_row = m_lar_solver.r_basis()[c.var()]; + if (is_nl_var(basic_in_row)) + return true; + } + return false; +} + +void core::collect_statistics(::statistics & st) { + st.update("arith-nla-explanations", m_stats.m_nla_explanations); + st.update("arith-nla-lemmas", m_stats.m_nla_lemmas); + st.update("arith-nra-calls", m_stats.m_nra_calls); +} + + +} // end of nla + diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index d0dc8d77d..0f1990933 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -18,15 +18,18 @@ #include "math/lp/nla_basics_lemmas.h" #include "math/lp/nla_order_lemmas.h" #include "math/lp/nla_monotone_lemmas.h" +#include "math/lp/nla_grobner.h" #include "math/lp/emonics.h" #include "math/lp/nla_settings.h" #include "math/lp/nex.h" #include "math/lp/horner.h" #include "math/lp/monomial_bounds.h" #include "math/lp/nla_intervals.h" -#include "math/grobner/pdd_solver.h" #include "nlsat/nlsat_solver.h" +namespace nra { + class solver; +} namespace nla { @@ -139,6 +142,20 @@ struct pp_factorization { }; class core { + friend struct common; + friend class new_lemma; + friend class grobner; + friend class order; + friend struct basics; + friend struct tangents; + friend class monotone; + friend struct nla_settings; + friend class intervals; + friend class horner; + friend class solver; + friend class monomial_bounds; + friend class nra::solver; + struct stats { unsigned m_nla_explanations; unsigned m_nla_lemmas; @@ -148,16 +165,18 @@ class core { memset(this, 0, sizeof(*this)); } }; - stats m_stats; - friend class new_lemma; - unsigned m_nlsat_delay { 50 }; - unsigned m_nlsat_fails { 0 }; + stats m_stats; + unsigned m_nlsat_delay = 50; + unsigned m_nlsat_fails = 0; + bool should_run_bounded_nlsat(); lbool bounded_nlsat(); -public: + var_eqs m_evars; + lp::lar_solver& m_lar_solver; + reslimit& m_reslim; vector * m_lemma_vec; lp::u_set m_to_refine; tangents m_tangents; @@ -166,24 +185,21 @@ public: monotone m_monotone; intervals m_intervals; monomial_bounds m_monomial_bounds; + nla_settings m_nla_settings; + horner m_horner; - nla_settings m_nla_settings; - dd::pdd_manager m_pdd_manager; - dd::solver m_pdd_grobner; -private: + grobner m_grobner; emonics m_emons; svector m_add_buffer; mutable lp::u_set m_active_var_set; - lp::u_set m_rows; + reslimit m_nra_lim; -public: - reslimit& m_reslim; - bool m_use_nra_model; + + bool m_use_nra_model = false; nra::solver m_nra; -private: - bool m_cautious_patching; - lpvar m_patched_var; - monic const* m_patched_monic; + bool m_cautious_patching = true; + lpvar m_patched_var = 0; + monic const* m_patched_monic = nullptr; void check_weighted(unsigned sz, std::pair>* checks); @@ -205,6 +221,8 @@ public: m_active_var_set.resize(m_lar_solver.number_of_vars()); } + unsigned get_var_weight(lpvar) const; + reslimit& reslim() { return m_reslim; } emonics& emons() { return m_emons; } const emonics& emons() const { return m_emons; } @@ -243,12 +261,15 @@ public: // returns true if the combination of the Horner's schema and Grobner Basis should be called bool need_run_horner() const { - return m_nla_settings.run_horner() && lp_settings().stats().m_nla_calls % m_nla_settings.horner_frequency() == 0; + return m_nla_settings.run_horner && lp_settings().stats().m_nla_calls % m_nla_settings.horner_frequency == 0; } bool need_run_grobner() const { - return m_nla_settings.run_grobner() && lp_settings().stats().m_nla_calls % m_nla_settings.grobner_frequency() == 0; + return m_nla_settings.run_grobner && lp_settings().stats().m_nla_calls % m_nla_settings.grobner_frequency == 0; } + + void set_active_vars_weights(nex_creator&); + std::unordered_set get_vars_of_expr_with_opening_terms(const nex* e); void incremental_linearization(bool); @@ -450,31 +471,19 @@ public: lpvar map_to_root(lpvar) const; std::ostream& print_terms(std::ostream&) const; std::ostream& print_term(const lp::lar_term&, std::ostream&) const; + template - std::ostream& print_row(const T & row , std::ostream& out) const { + std::ostream& print_row(const T& row, std::ostream& out) const { vector> v; for (auto p : row) { v.push_back(std::make_pair(p.coeff(), p.var())); } - return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, - out); + return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, out); } - void run_grobner(); - void find_nl_cluster(); - void prepare_rows_and_active_vars(); - void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); - std::unordered_set get_vars_of_expr_with_opening_terms(const nex* e); - void display_matrix_of_m_rows(std::ostream & out) const; - void set_active_vars_weights(nex_creator&); - unsigned get_var_weight(lpvar) const; - void add_row_to_grobner(const vector> & row); - bool check_pdd_eq(const dd::solver::equation*); - const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); - dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); - void set_level2var_for_grobner(); - void configure_grobner(); + bool influences_nl_var(lpvar) const; bool is_nl_var(lpvar) const; + bool is_used_in_monic(lpvar) const; void patch_monomials(); void patch_monomials_on_to_refine(); diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp new file mode 100644 index 000000000..51604fb47 --- /dev/null +++ b/src/math/lp/nla_grobner.cpp @@ -0,0 +1,545 @@ + /*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + nla_grobner.cpp + +Author: + Lev Nachmanson (levnach) + Nikolaj Bjorner (nbjorner) + +--*/ +#include "util/uint_set.h" +#include "math/lp/nla_core.h" +#include "math/lp/factorization_factory_imp.h" +#include "math/lp/nex.h" +#include "math/grobner/pdd_solver.h" +#include "math/dd/pdd_interval.h" +#include "math/dd/pdd_eval.h" + +namespace nla { + + grobner::grobner(core* c): + common(c), + m_pdd_manager(m_core.m_lar_solver.number_of_vars()), + m_solver(m_core.m_reslim, m_pdd_manager), + m_lar_solver(m_core.m_lar_solver) + + {} + + lp::lp_settings& grobner::lp_settings() { + return c().lp_settings(); + } + + void grobner::operator()() { + unsigned& quota = c().m_nla_settings.grobner_quota; + if (quota == 1) + return; + + lp_settings().stats().m_grobner_calls++; + find_nl_cluster(); + configure(); + m_solver.saturate(); + + if (is_conflicting()) + return; + + if (propagate_bounds()) + return; + + if (propagate_eqs()) + return; + + if (propagate_factorization()) + return; + + if (quota > 1) + quota--; + + IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); + IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); + +#if 0 + // diagnostics: did we miss something + vector eqs; + for (auto eq : m_solver.equations()) + eqs.push_back(eq->poly()); + c().m_nra.check(eqs); +#endif + } + + bool grobner::is_conflicting() { + unsigned conflicts = 0; + for (auto eq : m_solver.equations()) + if (is_conflicting(*eq) && ++conflicts >= m_solver.number_of_conflicts_to_report()) + break; + + if (conflicts > 0) + lp_settings().stats().m_grobner_conflicts++; + + TRACE("grobner", m_solver.display(tout)); + IF_VERBOSE(2, if (conflicts > 0) verbose_stream() << "grobner conflict\n"); + + return conflicts > 0; + } + + bool grobner::propagate_bounds() { + unsigned changed = 0; + for (auto eq : m_solver.equations()) + if (propagate_bounds(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) + return true; + return changed > 0; + } + + bool grobner::propagate_eqs() { + unsigned changed = 0; + for (auto eq : m_solver.equations()) + if (propagate_fixed(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) + return true; + return changed > 0; + } + + bool grobner::propagate_factorization() { + unsigned changed = 0; + for (auto eq : m_solver.equations()) + if (propagate_factorization(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) + return true; + return changed > 0; + } + + /** + \brief detect equalities + - k*x = 0, that is x = 0 + - ax + b = 0 + */ + typedef lp::lar_term term; + bool grobner::propagate_fixed(const dd::solver::equation& eq) { + dd::pdd const& p = eq.poly(); + //IF_VERBOSE(0, verbose_stream() << p << "\n"); + if (p.is_unary()) { + unsigned v = p.var(); + if (c().var_is_fixed(v)) + return false; + ineq new_eq(v, llc::EQ, rational::zero()); + if (c().ineq_holds(new_eq)) + return false; + new_lemma lemma(c(), "pdd-eq"); + add_dependencies(lemma, eq); + lemma |= new_eq; + return true; + } + if (p.is_offset()) { + unsigned v = p.var(); + if (c().var_is_fixed(v)) + return false; + rational a = p.hi().val(); + rational b = -p.lo().val(); + rational d = lcm(denominator(a), denominator(b)); + a *= d; + b *= d; + ineq new_eq(term(a, v), llc::EQ, b); + if (c().ineq_holds(new_eq)) + return false; + new_lemma lemma(c(), "pdd-eq"); + add_dependencies(lemma, eq); + lemma |= new_eq; + return true; + } + + return false; + } + + /** + \brief detect simple factors + x*q = 0 => x = 0 or q = 0 + */ + + bool grobner::propagate_factorization(const dd::solver::equation& eq) { + dd::pdd const& p = eq.poly(); + auto [vars, q] = p.var_factors(); + if (vars.empty() || !q.is_linear()) + return false; + + // IF_VERBOSE(0, verbose_stream() << "factored " << q << " : " << vars << "\n"); + + term t; + while (!q.is_val()) { + t.add_monomial(q.hi().val(), q.var()); + q = q.lo(); + } + vector ineqs; + for (auto v : vars) + ineqs.push_back(ineq(v, llc::EQ, rational::zero())); + ineqs.push_back(ineq(t, llc::EQ, -q.val())); + for (auto const& i : ineqs) + if (c().ineq_holds(i)) + return false; + + new_lemma lemma(c(), "pdd-factored"); + add_dependencies(lemma, eq); + for (auto const& i : ineqs) + lemma |= i; + //lemma.display(verbose_stream()); + return true; + } + + + void grobner::add_dependencies(new_lemma& lemma, const dd::solver::equation& eq) { + lp::explanation ex; + u_dependency_manager dm; + vector lv; + dm.linearize(eq.dep(), lv); + for (unsigned ci : lv) + ex.push_back(ci); + lemma &= ex; + } + + void grobner::configure() { + m_solver.reset(); + try { + set_level2var(); + TRACE("grobner", + tout << "base vars: "; + for (lpvar j : c().active_var_set()) + if (m_lar_solver.is_base(j)) + tout << "j" << j << " "; + tout << "\n"); + for (lpvar j : c().active_var_set()) { + if (m_lar_solver.is_base(j)) + add_row(m_lar_solver.basic2row(j)); + + if (c().is_monic_var(j) && c().var_is_fixed(j)) + add_fixed_monic(j); + } + } + catch (...) { + IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); + return; + } + TRACE("grobner", m_solver.display(tout)); + +#if 0 + IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); + dd::pdd_eval eval(m_pdd_manager); + eval.var2val() = [&](unsigned j){ return val(j); }; + for (auto* e : m_pdd_grobner.equations()) { + dd::pdd p = e->poly(); + rational v = eval(p); + if (p.is_linear() && !eval(p).is_zero()) { + IF_VERBOSE(0, verbose_stream() << "violated linear constraint " << p << "\n"); + } + } +#endif + + struct dd::solver::config cfg; + cfg.m_max_steps = m_solver.equations().size(); + cfg.m_max_simplified = c().m_nla_settings.grobner_max_simplified; + cfg.m_eqs_growth = c().m_nla_settings.grobner_eqs_growth; + cfg.m_expr_size_growth = c().m_nla_settings.grobner_expr_size_growth; + cfg.m_expr_degree_growth = c().m_nla_settings.grobner_expr_degree_growth; + cfg.m_number_of_conflicts_to_report = c().m_nla_settings.grobner_number_of_conflicts_to_report; + m_solver.set(cfg); + m_solver.adjust_cfg(); + m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. + } + + std::ostream& grobner::diagnose_pdd_miss(std::ostream& out) { + + // m_pdd_grobner.display(out); + + dd::pdd_eval eval; + eval.var2val() = [&](unsigned j){ return val(j); }; + for (auto* e : m_solver.equations()) { + dd::pdd p = e->poly(); + rational v = eval(p); + if (!v.is_zero()) { + out << p << " := " << v << "\n"; + } + } + + for (unsigned j = 0; j < m_lar_solver.number_of_vars(); ++j) { + if (m_lar_solver.column_has_lower_bound(j) || m_lar_solver.column_has_upper_bound(j)) { + out << j << ": ["; + if (m_lar_solver.column_has_lower_bound(j)) out << m_lar_solver.get_lower_bound(j); + out << ".."; + if (m_lar_solver.column_has_upper_bound(j)) out << m_lar_solver.get_upper_bound(j); + out << "]\n"; + } + } + return out; + } + + bool grobner::is_conflicting(const dd::solver::equation& e) { + auto& di = c().m_intervals.get_dep_intervals(); + dd::pdd_interval eval(di); + eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { + if (deps) c().m_intervals.set_var_interval(j, a); + else c().m_intervals.set_var_interval(j, a); + }; + scoped_dep_interval i(di), i_wd(di); + eval.get_interval(e.poly(), i); + if (!di.separated_from_zero(i)) { + TRACE("grobner", m_solver.display(tout << "not separated from 0 ", e) << "\n"; + eval.get_interval_distributed(e.poly(), i); + tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; + for (auto j : e.poly().free_vars()) { + scoped_dep_interval a(di); + c().m_intervals.set_var_interval(j, a); + c().m_intervals.display(tout << "j" << j << " ", a); tout << " "; + } + tout << "\n"); + + return false; + } + eval.get_interval(e.poly(), i_wd); + std::function f = [this](const lp::explanation& e) { + new_lemma lemma(m_core, "pdd"); + lemma &= e; + }; + if (di.check_interval_for_conflict_on_zero(i_wd, e.dep(), f)) { + TRACE("grobner", m_solver.display(tout << "conflict ", e) << "\n"); + return true; + } + else { + TRACE("grobner", m_solver.display(tout << "no conflict ", e) << "\n"); + return false; + } + } + + bool grobner::propagate_bounds(const dd::solver::equation& e) { + return false; + // TODO + auto& di = c().m_intervals.get_dep_intervals(); + dd::pdd_interval eval(di); + eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { + if (deps) c().m_intervals.set_var_interval(j, a); + else c().m_intervals.set_var_interval(j, a); + }; + scoped_dep_interval i(di), i_wd(di); + eval.get_interval(e.poly(), i); + return false; + } + + void grobner::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { + if (c().active_var_set_contains(j)) + return; + c().insert_to_active_var_set(j); + if (c().is_monic_var(j)) { + const monic& m = c().emons()[j]; + for (auto fcn : factorization_factory_imp(m, m_core)) + for (const factor& fc: fcn) + q.push_back(var(fc)); + } + + if (c().var_is_fixed(j)) + return; + const auto& matrix = m_lar_solver.A_r(); + for (auto & s : matrix.m_columns[j]) { + unsigned row = s.var(); + if (m_rows.contains(row)) + continue; + m_rows.insert(row); + unsigned k = m_lar_solver.get_base_column_in_row(row); + if (m_lar_solver.column_is_free(k) && k != j) + continue; + CTRACE("grobner", matrix.m_rows[row].size() > c().m_nla_settings.grobner_row_length_limit, + tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); + if (matrix.m_rows[row].size() > c().m_nla_settings.grobner_row_length_limit) + continue; + for (auto& rc : matrix.m_rows[row]) + add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); + } + } + + const rational& grobner::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { + unsigned lc, uc; + m_lar_solver.get_bound_constraint_witnesses_for_column(j, lc, uc); + dep = c().m_intervals.mk_join(dep, c().m_intervals.mk_leaf(lc)); + dep = c().m_intervals.mk_join(dep, c().m_intervals.mk_leaf(uc)); + return m_lar_solver.column_lower_bound(j).x; + } + + dd::pdd grobner::pdd_expr(const rational& coeff, lpvar j, u_dependency*& dep) { + dd::pdd r = m_pdd_manager.mk_val(coeff); + sbuffer vars; + vars.push_back(j); + u_dependency* zero_dep = dep; + while (!vars.empty()) { + j = vars.back(); + vars.pop_back(); + if (c().m_nla_settings.grobner_subs_fixed > 0 && c().var_is_fixed_to_zero(j)) { + r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); + dep = zero_dep; + return r; + } + if (c().m_nla_settings.grobner_subs_fixed == 1 && c().var_is_fixed(j)) + r *= val_of_fixed_var_with_deps(j, dep); + else if (!c().is_monic_var(j)) + r *= m_pdd_manager.mk_var(j); + else + for (lpvar k : c().emons()[j].vars()) + vars.push_back(k); + } + return r; + } + + /** + \brief convert p == 0 into a solved form v == r, such that + v has bounds [lo, oo) iff r has bounds [lo', oo) + v has bounds (oo,hi] iff r has bounds (oo,hi'] + + The solved form allows the Grobner solver identify more bounds conflicts. + A bad leading term can miss bounds conflicts. + For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] + we prefer to solve z == -x - y instead of x == -z - y + because the solution -z - y has neither an upper, nor a lower bound. + */ + bool grobner::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { + if (!p.is_linear()) + return false; + r = p; + unsigned num_lo = 0, num_hi = 0; + unsigned lo = 0, hi = 0; + rational lc, hc, c; + while (!r.is_val()) { + SASSERT(r.hi().is_val()); + v = r.var(); + rational val = r.hi().val(); + switch (m_lar_solver.get_column_type(v)) { + case lp::column_type::lower_bound: + if (val > 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::upper_bound: + if (val < 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::fixed: + case lp::column_type::boxed: + break; + default: + return false; + } + if (num_lo > 1 && num_hi > 1) + return false; + r = r.lo(); + } + if (num_lo == 1 && num_hi > 1) { + v = lo; + c = lc; + } + else if (num_hi == 1 && num_lo > 1) { + v = hi; + c = hc; + } + else + return false; + + r = c*m_pdd_manager.mk_var(v) - p; + if (c != 1) + r = r * (1/c); + return true; + } + + /** + \brief add an equality to grobner solver, convert it to solved form if available. + */ + void grobner::add_eq(dd::pdd& p, u_dependency* dep) { + unsigned v; + dd::pdd q(m_pdd_manager); + m_solver.simplify(p, dep); + if (is_solved(p, v, q)) + m_solver.add_subst(v, q, dep); + else + m_solver.add(p, dep); + } + + void grobner::add_fixed_monic(unsigned j) { + u_dependency* dep = nullptr; + dd::pdd r = m_pdd_manager.mk_val(rational(1)); + for (lpvar k : c().emons()[j].vars()) + r *= pdd_expr(rational::one(), k, dep); + r -= val_of_fixed_var_with_deps(j, dep); + add_eq(r, dep); + } + + void grobner::add_row(const vector> & row) { + u_dependency *dep = nullptr; + rational val; + dd::pdd sum = m_pdd_manager.mk_val(rational(0)); + for (const auto &p : row) + sum += pdd_expr(p.coeff(), p.var(), dep); + TRACE("grobner", c().print_row(row, tout) << " " << sum << "\n"); + add_eq(sum, dep); + } + + + void grobner::find_nl_cluster() { + prepare_rows_and_active_vars(); + svector q; + TRACE("grobner", for (lpvar j : c().m_to_refine) print_monic(c().emons()[j], tout) << "\n";); + + for (lpvar j : c().m_to_refine) + q.push_back(j); + + while (!q.empty()) { + lpvar j = q.back(); + q.pop_back(); + add_var_and_its_factors_to_q_and_collect_new_rows(j, q); + } + TRACE("grobner", tout << "vars in cluster: "; + for (lpvar j : c().active_var_set()) tout << "j" << j << " "; tout << "\n"; + display_matrix_of_m_rows(tout); + ); + } + + void grobner::prepare_rows_and_active_vars() { + m_rows.clear(); + m_rows.resize(m_lar_solver.row_count()); + c().clear_and_resize_active_var_set(); + } + + + void grobner::display_matrix_of_m_rows(std::ostream & out) const { + const auto& matrix = m_lar_solver.A_r(); + out << m_rows.size() << " rows" << "\n"; + out << "the matrix\n"; + for (const auto & r : matrix.m_rows) + c().print_row(r, out) << std::endl; + } + + void grobner::set_level2var() { + unsigned n = m_lar_solver.column_count(); + unsigned_vector sorted_vars(n), weighted_vars(n); + for (unsigned j = 0; j < n; j++) { + sorted_vars[j] = j; + weighted_vars[j] = c().get_var_weight(j); + } +#if 1 + // potential update to weights + for (unsigned j = 0; j < n; j++) { + if (c().is_monic_var(j) && c().m_to_refine.contains(j)) { + for (lpvar k : c().m_emons[j].vars()) { + weighted_vars[k] += 6; + } + } + } +#endif + + std::sort(sorted_vars.begin(), sorted_vars.end(), [&](unsigned a, unsigned b) { + unsigned wa = weighted_vars[a]; + unsigned wb = weighted_vars[b]; + return wa < wb || (wa == wb && a < b); }); + + unsigned_vector l2v(n); + for (unsigned j = 0; j < n; j++) + l2v[j] = sorted_vars[j]; + + m_pdd_manager.reset(l2v); + + TRACE("grobner", + for (auto v : sorted_vars) + tout << "j" << v << " w:" << weighted_vars[v] << " "; + tout << "\n"); + } + +} diff --git a/src/math/lp/nla_grobner.h b/src/math/lp/nla_grobner.h new file mode 100644 index 000000000..902ad3a46 --- /dev/null +++ b/src/math/lp/nla_grobner.h @@ -0,0 +1,64 @@ +/*++ + Copyright (c) 2017 Microsoft Corporation + + Author: + Nikolaj Bjorner (nbjorner) + Lev Nachmanson (levnach) + + --*/ +#pragma once + +#include "math/lp/nla_common.h" +#include "math/lp/nla_intervals.h" +#include "math/lp/nex.h" +#include "math/lp/cross_nested.h" +#include "math/lp/u_set.h" +#include "math/grobner/pdd_solver.h" + +namespace nla { + class core; + + class grobner : common { + dd::pdd_manager m_pdd_manager; + dd::solver m_solver; + lp::lar_solver& m_lar_solver; + lp::u_set m_rows; + + lp::lp_settings& lp_settings(); + + // solving + bool is_conflicting(); + bool is_conflicting(const dd::solver::equation& eq); + + bool propagate_bounds(); + bool propagate_bounds(const dd::solver::equation& eq); + + bool propagate_eqs(); + bool propagate_fixed(const dd::solver::equation& eq); + + bool propagate_factorization(); + bool propagate_factorization(const dd::solver::equation& eq); + + void add_dependencies(new_lemma& lemma, const dd::solver::equation& eq); + + // setup + void configure(); + void set_level2var(); + void find_nl_cluster(); + void prepare_rows_and_active_vars(); + void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); + void add_row(const vector>& row); + void add_fixed_monic(unsigned j); + bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); + void add_eq(dd::pdd& p, u_dependency* dep); + const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); + dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*& dep); + + void display_matrix_of_m_rows(std::ostream& out) const; + std::ostream& diagnose_pdd_miss(std::ostream& out); + + public: + grobner(core *core); + void operator()(); + }; +} diff --git a/src/math/lp/nla_order_lemmas.cpp b/src/math/lp/nla_order_lemmas.cpp index a7910601f..94ddc4d9b 100644 --- a/src/math/lp/nla_order_lemmas.cpp +++ b/src/math/lp/nla_order_lemmas.cpp @@ -19,7 +19,7 @@ typedef lp::lar_term term; // a > b && c > 0 => ac > bc void order::order_lemma() { TRACE("nla_solver", ); - if (!c().m_nla_settings.run_order()) { + if (!c().m_nla_settings.run_order) { TRACE("nla_solver", tout << "not generating order lemmas\n";); return; } diff --git a/src/math/lp/nla_settings.h b/src/math/lp/nla_settings.h index 4045f4567..ec11ea5b2 100644 --- a/src/math/lp/nla_settings.h +++ b/src/math/lp/nla_settings.h @@ -9,94 +9,38 @@ Author: #pragma once namespace nla { -class nla_settings { - bool m_run_order; - bool m_run_tangents; - bool m_run_horner; - // how often to call the horner heuristic - unsigned m_horner_frequency; - unsigned m_horner_row_length_limit; - unsigned m_horner_subs_fixed; - // grobner fields - bool m_run_grobner; - unsigned m_grobner_row_length_limit; - unsigned m_grobner_subs_fixed; - unsigned m_grobner_eqs_growth; - unsigned m_grobner_tree_size_growth; - unsigned m_grobner_expr_size_growth; - unsigned m_grobner_expr_degree_growth; - unsigned m_grobner_max_simplified; - unsigned m_grobner_number_of_conflicts_to_report; - unsigned m_grobner_quota; - unsigned m_grobner_frequency; - bool m_run_nra; - // expensive patching - bool m_expensive_patching; -public: - nla_settings() : m_run_order(true), - m_run_tangents(true), - m_run_horner(true), - m_horner_frequency(4), - m_horner_row_length_limit(10), - m_horner_subs_fixed(2), - m_run_grobner(true), - m_grobner_row_length_limit(50), - m_grobner_subs_fixed(false), - m_grobner_quota(0), - m_grobner_frequency(4), - m_run_nra(false), - m_expensive_patching(false) - {} - unsigned grobner_eqs_growth() const { return m_grobner_eqs_growth;} - unsigned& grobner_eqs_growth() { return m_grobner_eqs_growth;} - bool run_order() const { return m_run_order; } - bool& run_order() { return m_run_order; } + struct nla_settings { + bool run_order = true; + bool run_tangents = true; + + // horner fields + bool run_horner = true; + unsigned horner_frequency = 4; + unsigned horner_row_length_limit = 10; + unsigned horner_subs_fixed = 2; - bool run_tangents() const { return m_run_tangents; } - bool& run_tangents() { return m_run_tangents; } + + // grobner fields + bool run_grobner = true; + unsigned grobner_row_length_limit = 50; + unsigned grobner_subs_fixed = 1; + unsigned grobner_eqs_growth = 10; + unsigned grobner_tree_size_growth = 2; + unsigned grobner_expr_size_growth = 2; + unsigned grobner_expr_degree_growth = 2; + unsigned grobner_max_simplified = 10000; + unsigned grobner_number_of_conflicts_to_report = 1; + unsigned grobner_quota = 0; + unsigned grobner_frequency = 4; - bool expensive_patching() const { return m_expensive_patching; } - bool& expensive_patching() { return m_expensive_patching; } - bool run_horner() const { return m_run_horner; } - bool& run_horner() { return m_run_horner; } - - unsigned horner_frequency() const { return m_horner_frequency; } - unsigned& horner_frequency() { return m_horner_frequency; } - unsigned horner_row_length_limit() const { return m_horner_row_length_limit; } - unsigned& horner_row_length_limit() { return m_horner_row_length_limit; } - unsigned horner_subs_fixed() const { return m_horner_subs_fixed; } - unsigned& horner_subs_fixed() { return m_horner_subs_fixed; } - - bool run_grobner() const { return m_run_grobner; } - bool& run_grobner() { return m_run_grobner; } - unsigned grobner_frequency() const { return m_grobner_frequency; } - unsigned& grobner_frequency() { return m_grobner_frequency; } - - bool run_nra() const { return m_run_nra; } - bool& run_nra() { return m_run_nra; } - - unsigned grobner_row_length_limit() const { return m_grobner_row_length_limit; } - unsigned& grobner_row_length_limit() { return m_grobner_row_length_limit; } - unsigned grobner_subs_fixed() const { return m_grobner_subs_fixed; } - unsigned& grobner_subs_fixed() { return m_grobner_subs_fixed; } - - unsigned grobner_tree_size_growth() const { return m_grobner_tree_size_growth; } - unsigned & grobner_tree_size_growth() { return m_grobner_tree_size_growth; } - - unsigned grobner_expr_size_growth() const { return m_grobner_expr_size_growth; } - unsigned & grobner_expr_size_growth() { return m_grobner_expr_size_growth; } - - unsigned grobner_expr_degree_growth() const { return m_grobner_expr_degree_growth; } - unsigned & grobner_expr_degree_growth() { return m_grobner_expr_degree_growth; } - - unsigned grobner_max_simplified() const { return m_grobner_max_simplified; } - unsigned & grobner_max_simplified() { return m_grobner_max_simplified; } - - unsigned grobner_number_of_conflicts_to_report() const { return m_grobner_number_of_conflicts_to_report; } - unsigned & grobner_number_of_conflicts_to_report() { return m_grobner_number_of_conflicts_to_report; } - - unsigned& grobner_quota() { return m_grobner_quota; } + // nra fields + bool run_nra = false; -}; + // expensive patching + bool expensive_patching = false; + + nla_settings() {} + + }; } diff --git a/src/math/lp/nla_tangent_lemmas.cpp b/src/math/lp/nla_tangent_lemmas.cpp index d01dc51ca..299d8031f 100644 --- a/src/math/lp/nla_tangent_lemmas.cpp +++ b/src/math/lp/nla_tangent_lemmas.cpp @@ -186,7 +186,7 @@ tangents::tangents(core * c) : common(c) {} void tangents::tangent_lemma() { factorization bf(nullptr); const monic* m = nullptr; - if (c().m_nla_settings.run_tangents() && c().find_bfc_to_refine(m, bf)) { + if (c().m_nla_settings.run_tangents && c().find_bfc_to_refine(m, bf)) { lpvar j = m->var(); tangent_imp tangent(point(val(bf[0]), val(bf[1])), c().val(j), *m, bf, *this); tangent(); diff --git a/src/math/lp/nra_solver.cpp b/src/math/lp/nra_solver.cpp index 31c8d2f11..56a84d1f0 100644 --- a/src/math/lp/nra_solver.cpp +++ b/src/math/lp/nra_solver.cpp @@ -65,12 +65,10 @@ struct solver::imp { } // add polynomial definitions. - for (auto const& m : m_nla_core.emons()) { + for (auto const& m : m_nla_core.emons()) add_monic_eq(m); - } - for (unsigned i : m_term_set) { + for (unsigned i : m_term_set) add_term(i); - } // TBD: add variable bounds? lbool r = l_undef; @@ -176,7 +174,103 @@ struct solver::imp { lp_assert(false); // unreachable } m_nlsat->mk_clause(1, &lit, a); - } + } + + + lbool check(vector const& eqs) { + m_zero = nullptr; + m_nlsat = alloc(nlsat::solver, m_limit, m_params, false); + m_zero = alloc(scoped_anum, am()); + m_lp2nl.reset(); + m_term_set.clear(); + for (auto const& eq : eqs) + add_eq(eq); + for (auto const& [v, w] : m_lp2nl) { + auto& ls = m_nla_core.m_lar_solver; + if (ls.column_has_lower_bound(v)) + add_lb(ls.get_lower_bound(v), w); + if (ls.column_has_upper_bound(v)) + add_ub(ls.get_upper_bound(v), w); + } + + lbool r = l_undef; + try { + r = m_nlsat->check(); + } + catch (z3_exception&) { + if (m_limit.is_canceled()) { + r = l_undef; + } + else { + throw; + } + } + + IF_VERBOSE(0, verbose_stream() << "check-nra " << r << "\n"; + m_nlsat->display(verbose_stream()); + for (auto const& [v, w] : m_lp2nl) { + auto& ls = m_nla_core.m_lar_solver; + if (ls.column_has_lower_bound(v)) + verbose_stream() << w << " >= " << ls.get_lower_bound(v) << "\n"; + if (ls.column_has_upper_bound(v)) + verbose_stream() << w << " <= " << ls.get_upper_bound(v) << "\n"; + }); + + + return r; + } + + void add_eq(dd::pdd const& eq) { + dd::pdd normeq = eq; + rational lc(1); + for (auto const& [c, m] : eq) + lc = lcm(denominator(c), lc); + if (lc != 1) + normeq *= lc; + polynomial::manager& pm = m_nlsat->pm(); + polynomial::polynomial_ref p(pdd2polynomial(normeq), pm); + bool is_even[1] = { false }; + polynomial::polynomial* ps[1] = { p }; + nlsat::literal lit = m_nlsat->mk_ineq_literal(nlsat::atom::kind::EQ, 1, ps, is_even); + m_nlsat->mk_clause(1, &lit, nullptr); + } + + void add_lb(lp::impq const& b, unsigned w) { + add_bound(b.x, w, b.y <= 0, b.y > 0 ? nlsat::atom::kind::GT : nlsat::atom::kind::LT); + } + void add_ub(lp::impq const& b, unsigned w) { + add_bound(b.x, w, b.y >= 0, b.y < 0 ? nlsat::atom::kind::LT : nlsat::atom::kind::GT); + } + + // w - bound < 0 + // w - bound > 0 + void add_bound(lp::mpq const& bound, unsigned w, bool neg, nlsat::atom::kind k) { + polynomial::manager& pm = m_nlsat->pm(); + polynomial::polynomial_ref p1(pm.mk_polynomial(w), pm); + polynomial::polynomial_ref p2(pm.mk_const(bound), pm); + polynomial::polynomial_ref p(pm.sub(p1, p2), pm); + polynomial::polynomial* ps[1] = { p }; + bool is_even[1] = { false }; + nlsat::literal lit = m_nlsat->mk_ineq_literal(k, 1, ps, is_even); + if (neg) + lit.neg(); + m_nlsat->mk_clause(1, &lit, nullptr); + } + + polynomial::polynomial* pdd2polynomial(dd::pdd const& p) { + polynomial::manager& pm = m_nlsat->pm(); + if (p.is_val()) + return pm.mk_const(p.val()); + polynomial::polynomial_ref lo(pdd2polynomial(p.lo()), pm); + polynomial::polynomial_ref hi(pdd2polynomial(p.hi()), pm); + unsigned w, v = p.var(); + if (!m_lp2nl.find(v, w)) { + w = m_nlsat->mk_var(false); + m_lp2nl.insert(v, w); + } + polynomial::polynomial_ref vp(pm.mk_polynomial(w, 1), pm); + return pm.add(lo, pm.mul(vp, hi)); + } bool is_int(lp::var_index v) { return s.var_is_int(v); @@ -265,6 +359,10 @@ lbool solver::check() { return m_imp->check(); } +lbool solver::check(vector const& eqs) { + return m_imp->check(eqs); +} + bool solver::need_check() { return m_imp->need_check(); } diff --git a/src/math/lp/nra_solver.h b/src/math/lp/nra_solver.h index 56dcd69b5..b8863e44b 100644 --- a/src/math/lp/nra_solver.h +++ b/src/math/lp/nra_solver.h @@ -9,6 +9,7 @@ #include "util/rlimit.h" #include "util/params.h" #include "nlsat/nlsat_solver.h" +#include "math/dd/dd_pdd.h" namespace lp { class lar_solver; @@ -36,6 +37,11 @@ namespace nra { */ lbool check(); + /** + \breif Check feasibility of equalities modulo bounds constraints on their variables. + */ + lbool check(vector const& eqs); + /* \brief determine whether nra check is needed. */ diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index fa2903c35..8c4ec6ed9 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -137,9 +137,6 @@ namespace opt { m_model_fixed(), m_objective_refs(m), m_core(m), - m_enable_sat(false), - m_is_clausal(false), - m_pp_neat(false), m_unknown("unknown") { params_ref p; @@ -196,6 +193,8 @@ namespace opt { void context::add_hard_constraint(expr* f) { if (m_calling_on_model) { + if (!m_incremental) + throw default_exception("Set opt.incremental = true to allow adding constraints during search"); get_solver().assert_expr(f); for (auto const& [k, v] : m_maxsmts) v->reset_upper(); @@ -838,19 +837,14 @@ namespace opt { } goal_ref g(alloc(goal, m, true, !asms.empty())); - for (expr* fml : fmls) { + for (expr* fml : fmls) g->assert_expr(fml); - } - for (expr * a : asms) { + for (expr * a : asms) g->assert_expr(a, a); - } tactic_ref tac0 = and_then(mk_simplify_tactic(m, m_params), mk_propagate_values_tactic(m), - mk_solve_eqs_tactic(m), - // NB: cannot ackermannize because max/min objectives would disappear - // mk_ackermannize_bv_tactic(m, m_params), - // NB: mk_elim_uncstr_tactic(m) is not sound with soft constraints + m_incremental ? mk_skip_tactic() : mk_solve_eqs_tactic(m), mk_simplify_tactic(m)); opt_params optp(m_params); tactic_ref tac1, tac2, tac3, tac4; @@ -861,7 +855,7 @@ namespace opt { m.linearize(core, deps); has_dep |= !deps.empty(); } - if (optp.elim_01() && m_logic.is_null() && !has_dep) { + if (optp.elim_01() && m_logic.is_null() && !has_dep && !m_incremental) { tac1 = mk_dt2bv_tactic(m); tac2 = mk_lia2card_tactic(m); tac3 = mk_eq2bv_tactic(m); @@ -1568,6 +1562,7 @@ namespace opt { m_maxsat_engine = _p.maxsat_engine(); m_pp_neat = _p.pp_neat(); m_pp_wcnf = _p.pp_wcnf(); + m_incremental = _p.incremental(); } std::string context::to_string() { diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index c02689a38..49cc6adcd 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -194,11 +194,12 @@ namespace opt { func_decl_ref_vector m_objective_refs; expr_ref_vector m_core; tactic_ref m_simplify; - bool m_enable_sat { true } ; - bool m_enable_sls { false }; - bool m_is_clausal { false }; - bool m_pp_neat { true }; - bool m_pp_wcnf { false }; + bool m_enable_sat = true; + bool m_enable_sls = false; + bool m_is_clausal = false; + bool m_pp_neat = false; + bool m_pp_wcnf = false; + bool m_incremental = false; symbol m_maxsat_engine; symbol m_logic; svector m_labels; diff --git a/src/opt/opt_params.pyg b/src/opt/opt_params.pyg index df3ab0925..893b4bfd6 100644 --- a/src/opt/opt_params.pyg +++ b/src/opt/opt_params.pyg @@ -15,6 +15,7 @@ def_module_params('opt', ('enable_core_rotate', BOOL, False, 'enable core rotation to both sample cores and correction sets'), ('enable_sat', BOOL, True, 'enable the new SAT core for propositional constraints'), ('elim_01', BOOL, True, 'eliminate 01 variables'), + ('incremental', BOOL, False, 'set incremental mode. It disables pre-processing and enables adding constraints in model event handler'), ('pp.neat', BOOL, True, 'use neat (as opposed to less readable, but faster) pretty printer when displaying context'), ('pb.compile_equality', BOOL, False, 'compile arithmetical equalities into pseudo-Boolean equality (instead of two inequalites)'), ('pp.wcnf', BOOL, False, 'print maxsat benchmark into wcnf format'), diff --git a/src/qe/qe_mbi.cpp b/src/qe/qe_mbi.cpp index 0261ad979..b261e44be 100644 --- a/src/qe/qe_mbi.cpp +++ b/src/qe/qe_mbi.cpp @@ -536,6 +536,7 @@ namespace qe { th_rewriter rewrite(m); rewrite(a); rewrite(b); + TRACE("interpolator", tout << a << " " << b << "\n"); solver_ref sA = sf(m, p, false /* no proofs */, true, true, symbol::null); solver_ref sB = sf(m, p, false /* no proofs */, true, true, symbol::null); solver_ref sNotA = sf(m, p, false /* no proofs */, true, true, symbol::null); diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp index ea936600c..409d03652 100644 --- a/src/sat/sat_simplifier.cpp +++ b/src/sat/sat_simplifier.cpp @@ -1787,6 +1787,7 @@ namespace sat { clause& c = it.curr(); if (!c.is_learned() && !c.was_removed()) { r.push_back(clause_wrapper(c)); + SASSERT(r.back().contains(l)); SASSERT(r.back().size() == c.size()); } } @@ -1808,9 +1809,13 @@ namespace sat { Return false if the result is a tautology */ bool simplifier::resolve(clause_wrapper const & c1, clause_wrapper const & c2, literal l, literal_vector & r) { - CTRACE("resolve_bug", !c1.contains(l), tout << c1 << "\n" << c2 << "\nl: " << l << "\n";); + CTRACE("resolve_bug", !c1.contains(l) || !c2.contains(~l), tout << c1 << "\n" << c2 << "\nl: " << l << "\n";); if (m_visited.size() <= 2*s.num_vars()) m_visited.resize(2*s.num_vars(), false); + if (c1.was_removed()) + return false; + if (c2.was_removed()) + return false; SASSERT(c1.contains(l)); SASSERT(c2.contains(~l)); bool res = true; @@ -1973,7 +1978,14 @@ namespace sat { } } } - TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n";); + TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n"; + tout << "pos\n"; + for (auto & c : m_pos_cls) + tout << c << "\n"; + tout << "neg\n"; + for (auto & c : m_neg_cls) + tout << c << "\n"; + ); m_elim_counter -= num_pos * num_neg + before_lits; m_elim_counter -= num_pos * num_neg + before_lits; @@ -1988,6 +2000,8 @@ namespace sat { m_elim_counter -= num_pos * num_neg + before_lits; for (auto & c1 : m_pos_cls) { + if (c1.was_removed()) + continue; for (auto & c2 : m_neg_cls) { m_new_cls.reset(); if (!resolve(c1, c2, pos_l, m_new_cls)) diff --git a/src/sat/smt/arith_internalize.cpp b/src/sat/smt/arith_internalize.cpp index 732cbfc6f..09352e147 100644 --- a/src/sat/smt/arith_internalize.cpp +++ b/src/sat/smt/arith_internalize.cpp @@ -69,23 +69,23 @@ namespace arith { m_nla->push(); } smt_params_helper prms(s().params()); - m_nla->settings().run_order() = prms.arith_nl_order(); - m_nla->settings().run_tangents() = prms.arith_nl_tangents(); - m_nla->settings().run_horner() = prms.arith_nl_horner(); - m_nla->settings().horner_subs_fixed() = prms.arith_nl_horner_subs_fixed(); - m_nla->settings().horner_frequency() = prms.arith_nl_horner_frequency(); - m_nla->settings().horner_row_length_limit() = prms.arith_nl_horner_row_length_limit(); - m_nla->settings().run_grobner() = prms.arith_nl_grobner(); - m_nla->settings().run_nra() = prms.arith_nl_nra(); - m_nla->settings().grobner_subs_fixed() = prms.arith_nl_grobner_subs_fixed(); - m_nla->settings().grobner_eqs_growth() = prms.arith_nl_grobner_eqs_growth(); - m_nla->settings().grobner_expr_size_growth() = prms.arith_nl_grobner_expr_size_growth(); - m_nla->settings().grobner_expr_degree_growth() = prms.arith_nl_grobner_expr_degree_growth(); - m_nla->settings().grobner_max_simplified() = prms.arith_nl_grobner_max_simplified(); - m_nla->settings().grobner_number_of_conflicts_to_report() = prms.arith_nl_grobner_cnfl_to_report(); - m_nla->settings().grobner_quota() = prms.arith_nl_gr_q(); - m_nla->settings().grobner_frequency() = prms.arith_nl_grobner_frequency(); - m_nla->settings().expensive_patching() = false; + m_nla->settings().run_order = prms.arith_nl_order(); + m_nla->settings().run_tangents = prms.arith_nl_tangents(); + m_nla->settings().run_horner = prms.arith_nl_horner(); + m_nla->settings().horner_subs_fixed = prms.arith_nl_horner_subs_fixed(); + m_nla->settings().horner_frequency = prms.arith_nl_horner_frequency(); + m_nla->settings().horner_row_length_limit = prms.arith_nl_horner_row_length_limit(); + m_nla->settings().run_grobner = prms.arith_nl_grobner(); + m_nla->settings().run_nra = prms.arith_nl_nra(); + m_nla->settings().grobner_subs_fixed = prms.arith_nl_grobner_subs_fixed(); + m_nla->settings().grobner_eqs_growth = prms.arith_nl_grobner_eqs_growth(); + m_nla->settings().grobner_expr_size_growth = prms.arith_nl_grobner_expr_size_growth(); + m_nla->settings().grobner_expr_degree_growth = prms.arith_nl_grobner_expr_degree_growth(); + m_nla->settings().grobner_max_simplified = prms.arith_nl_grobner_max_simplified(); + m_nla->settings().grobner_number_of_conflicts_to_report = prms.arith_nl_grobner_cnfl_to_report(); + m_nla->settings().grobner_quota = prms.arith_nl_gr_q(); + m_nla->settings().grobner_frequency = prms.arith_nl_grobner_frequency(); + m_nla->settings().expensive_patching = false; } } diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp index 3922f6d31..acec6ff99 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -442,6 +442,7 @@ namespace bv { SASSERT(bv.is_int2bv(n)); euf::enode* e = expr2enode(n); mk_bits(e->get_th_var(get_id())); + get_var(e->get_arg(0)); assert_int2bv_axiom(n); } diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index 1ad58c497..3612749f4 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -209,6 +209,22 @@ namespace bv { if (is_bv(eq.v1())) { m_find.merge(eq.v1(), eq.v2()); VERIFY(eq.is_eq()); + return; + } + euf::enode* n1 = var2enode(eq.v1()); + for (euf::enode* bv2int : euf::enode_class(n1)) { + if (!bv.is_bv2int(bv2int->get_expr())) + continue; + euf::enode* bv2int_arg = bv2int->get_arg(0); + for (euf::enode* p : euf::enode_parents(n1->get_root())) { + if (bv.is_int2bv(p->get_expr()) && p->get_sort() == bv2int_arg->get_sort() && p->get_root() != bv2int_arg->get_root()) { + euf::enode_pair_vector eqs; + eqs.push_back({ n1, p->get_arg(0) }); + eqs.push_back({ n1, bv2int }); + ctx.propagate(p, bv2int_arg, euf::th_explain::propagate(*this, eqs, p, bv2int_arg)); + break; + } + } } } diff --git a/src/sat/smt/dt_solver.cpp b/src/sat/smt/dt_solver.cpp index 13d9768e2..751c43210 100644 --- a/src/sat/smt/dt_solver.cpp +++ b/src/sat/smt/dt_solver.cpp @@ -506,7 +506,7 @@ namespace dt { return m_nodes; } - ptr_vector const& solver::get_seq_args(enode* n) { + ptr_vector const& solver::get_seq_args(enode* n, enode*& sibling) { m_nodes.reset(); m_todo.reset(); auto add_todo = [&](enode* n) { @@ -515,9 +515,15 @@ namespace dt { m_todo.push_back(n); } }; - - for (enode* sib : euf::enode_class(n)) - add_todo(sib); + + for (enode* sib : euf::enode_class(n)) { + if (m_sutil.str.is_concat_of_units(sib->get_expr())) { + add_todo(sib); + sibling = sib; + break; + } + } + for (unsigned i = 0; i < m_todo.size(); ++i) { enode* n = m_todo[i]; @@ -551,10 +557,10 @@ namespace dt { // collect equalities on all children that may have been used. bool found = false; - auto add = [&](enode* arg) { - if (arg->get_root() == child->get_root()) { - if (arg != child) - m_used_eqs.push_back(enode_pair(arg, child)); + auto add = [&](enode* seq_arg) { + if (seq_arg->get_root() == child->get_root()) { + if (seq_arg != child) + m_used_eqs.push_back(enode_pair(seq_arg, child)); found = true; } }; @@ -564,11 +570,14 @@ namespace dt { if (m_autil.is_array(s) && dt.is_datatype(get_array_range(s))) for (enode* aarg : get_array_args(arg)) add(aarg); - } - sort* se; - if (m_sutil.is_seq(child->get_sort(), se) && dt.is_datatype(se)) { - for (enode* aarg : get_seq_args(child)) - add(aarg); + sort* se; + if (m_sutil.is_seq(arg->get_sort(), se) && dt.is_datatype(se)) { + enode* sibling = nullptr; + for (enode* seq_arg : get_seq_args(arg, sibling)) + add(seq_arg); + if (sibling && sibling != arg) + m_used_eqs.push_back(enode_pair(arg, sibling)); + } } VERIFY(found); @@ -636,12 +645,13 @@ namespace dt { // explore `arg` (with parent) expr* earg = arg->get_expr(); sort* s = earg->get_sort(), *se; + enode* sibling; if (dt.is_datatype(s)) { m_parent.insert(arg->get_root(), parent); oc_push_stack(arg); } else if (m_sutil.is_seq(s, se) && dt.is_datatype(se)) { - for (enode* sarg : get_seq_args(arg)) + for (enode* sarg : get_seq_args(arg, sibling)) if (process_arg(sarg)) return true; } diff --git a/src/sat/smt/dt_solver.h b/src/sat/smt/dt_solver.h index e0a076a2d..4e2524f6b 100644 --- a/src/sat/smt/dt_solver.h +++ b/src/sat/smt/dt_solver.h @@ -112,7 +112,7 @@ namespace dt { void oc_push_stack(enode * n); ptr_vector m_nodes, m_todo; ptr_vector const& get_array_args(enode* n); - ptr_vector const& get_seq_args(enode* n); + ptr_vector const& get_seq_args(enode* n, enode*& sibling); void pop_core(unsigned n) override; diff --git a/src/sat/smt/pb_solver.cpp b/src/sat/smt/pb_solver.cpp index c92efdb7d..dc2bb71dd 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -1795,9 +1795,9 @@ namespace pb { } if (c.lit() != sat::null_literal && value(c.lit()) != l_true) return true; SASSERT(c.lit() == sat::null_literal || lvl(c.lit()) == 0 || (c.is_watched(*this, c.lit()) && c.is_watched(*this, ~c.lit()))); - if (eval(c) == l_true) { + if (eval(c) == l_true) return true; - } + literal_vector lits(c.literals()); for (literal l : lits) { if (lvl(l) == 0) continue; @@ -1823,6 +1823,8 @@ namespace pb { } bool solver::validate_watch(pbc const& p, literal alit) const { + if (value(p.lit()) != l_true) + return true; for (unsigned i = 0; i < p.size(); ++i) { literal l = p[i].second; if (l != alit && lvl(l) != 0 && p.is_watched(*this, l) != (i < p.num_watch())) { @@ -1833,9 +1835,8 @@ namespace pb { } } unsigned slack = 0; - for (unsigned i = 0; i < p.num_watch(); ++i) { - slack += p[i].first; - } + for (unsigned i = 0; i < p.num_watch(); ++i) + slack += p[i].first; if (slack != p.slack()) { IF_VERBOSE(0, display(verbose_stream(), p, true);); UNREACHABLE(); diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp index 6167d3b9b..7f4ff2f4d 100644 --- a/src/sat/smt/sat_th.cpp +++ b/src/sat/smt/sat_th.cpp @@ -270,6 +270,10 @@ namespace euf { return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), sat::null_literal, x, y, pma); } + th_explain* th_explain::propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma) { + return mk(th, 0, nullptr, eqs.size(), eqs.data(), sat::null_literal, x, y, pma); + } + th_explain* th_explain::propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y) { return mk(th, 1, &lit, 0, nullptr, sat::null_literal, x, y); } diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index f699c864b..4484a7717 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -241,6 +241,7 @@ namespace euf { static th_explain* conflict(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); static th_explain* conflict(th_euf_solver& th, euf::enode* x, euf::enode* y); static th_explain* propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); + static th_explain* propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index b90ba760d..0ae520d8a 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -72,7 +72,7 @@ def_module_params(module_name='smt', ('arith.nl.grobner_max_simplified', UINT, 10000, 'grobner\'s maximum number of simplifications'), ('arith.nl.grobner_cnfl_to_report', UINT, 1, 'grobner\'s maximum number of conflicts to report'), ('arith.nl.gr_q', UINT, 10, 'grobner\'s quota'), - ('arith.nl.grobner_subs_fixed', UINT, 2, '0 - no subs, 1 - substitute, 2 - substitute fixed zeros only'), + ('arith.nl.grobner_subs_fixed', UINT, 1, '0 - no subs, 1 - substitute, 2 - substitute fixed zeros only'), ('arith.nl.delay', UINT, 500, 'number of calls to final check before invoking bounded nlsat check'), ('arith.propagate_eqs', BOOL, True, 'propagate (cheap) equalities'), ('arith.propagation_mode', UINT, 1, '0 - no propagation, 1 - propagate existing literals, 2 - refine finite bounds'), diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 451136351..092bc0127 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -1429,7 +1429,10 @@ namespace smt { inc_ref(l2); m_watches[(~l1).index()].insert_literal(l2); m_watches[(~l2).index()].insert_literal(l1); - if (get_assignment(l2) == l_false) { + if (get_assignment(l1) == l_false) { + assign(l2, b_justification(~l1)); + } + else if (get_assignment(l2) == l_false) { assign(l1, b_justification(~l2)); } m_clause_proof.add(l1, l2, k, j); diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 5f6131f59..18a290d0a 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -258,7 +258,7 @@ namespace smt { bindings.set(num_decls - i - 1, sk_value); } - TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance: " << bindings << "\n" << defs << "\n";); + TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance: " << bindings << "\ndefs:\n" << defs << "\n";); if (!defs.empty()) def = mk_and(defs); max_generation = std::max(m_qm->get_generation(q), max_generation); add_instance(q, bindings, max_generation, def.get()); @@ -453,7 +453,7 @@ namespace smt { TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); m_max_cexs += m_params.m_mbqi_max_cexs; - if (num_failures == 0 && (!m_context->validate_model())) { + if (num_failures == 0 && !m_context->validate_model()) { num_failures = 1; // this time force expanding recursive function definitions // that are not forced true in the current model. diff --git a/src/smt/tactic/smt_tactic_core.cpp b/src/smt/tactic/smt_tactic_core.cpp index 9c5fc1c8e..c45165d79 100644 --- a/src/smt/tactic/smt_tactic_core.cpp +++ b/src/smt/tactic/smt_tactic_core.cpp @@ -348,6 +348,7 @@ public: m_eq_eh = nullptr; m_diseq_eh = nullptr; m_created_eh = nullptr; + m_decide_eh = nullptr; } void user_propagate_init( @@ -385,6 +386,10 @@ public: void user_propagate_register_created(user_propagator::created_eh_t& created_eh) override { m_created_eh = created_eh; } + + void user_propagate_register_decide(user_propagator::decide_eh_t& decide_eh) override { + m_decide_eh = decide_eh; + } }; static tactic * mk_seq_smt_tactic(ast_manager& m, params_ref const & p) { diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index c7a92b18c..012ad695b 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -492,6 +492,7 @@ namespace smt { void theory_arith::mk_axiom(expr * ante, expr * conseq, bool simplify_conseq) { th_rewriter & s = ctx.get_rewriter(); expr_ref s_ante(m), s_conseq(m); + expr_ref p_ante(ante, m), p_conseq(conseq, m); // pinned versions expr* s_conseq_n, * s_ante_n; bool negated; @@ -562,7 +563,7 @@ namespace smt { if (!m_util.is_zero(divisor)) { // if divisor is zero, then idiv and mod are uninterpreted functions. expr_ref div(m), mod(m), zero(m), abs_divisor(m), one(m); - expr_ref eqz(m), eq(m), lower(m), upper(m); + expr_ref eqz(m), eq(m), lower(m), upper(m), qr(m); div = m_util.mk_idiv(dividend, divisor); mod = m_util.mk_mod(dividend, divisor); zero = m_util.mk_int(0); @@ -570,7 +571,8 @@ namespace smt { abs_divisor = m_util.mk_sub(m.mk_ite(m_util.mk_lt(divisor, zero), m_util.mk_sub(zero, divisor), divisor), one); s(abs_divisor); eqz = m.mk_eq(divisor, zero); - eq = m.mk_eq(m_util.mk_add(m_util.mk_mul(divisor, div), mod), dividend); + qr = m_util.mk_add(m_util.mk_mul(divisor, div), mod); + eq = m.mk_eq(qr, dividend); lower = m_util.mk_ge(mod, zero); upper = m_util.mk_le(mod, abs_divisor); TRACE("div_axiom_bug", @@ -584,6 +586,8 @@ namespace smt { mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; + m_arith_eq_adapter.mk_axioms(ensure_enode(qr), ensure_enode(mod)); + if (m_util.is_zero(dividend)) { mk_axiom(eqz, m.mk_eq(div, zero)); mk_axiom(eqz, m.mk_eq(mod, zero)); @@ -591,7 +595,7 @@ namespace smt { // (or (= y 0) (<= (* y (div x y)) x)) else if (!m_util.is_numeral(divisor)) { - expr_ref div_ge(m), div_le(m), ge(m), le(m); + expr_ref div_ge(m); div_ge = m_util.mk_ge(m_util.mk_sub(dividend, m_util.mk_mul(divisor, div)), zero); s(div_ge); mk_axiom(eqz, div_ge, false); diff --git a/src/smt/theory_arith_nl.h b/src/smt/theory_arith_nl.h index 436dccc6a..3aa34cbc7 100644 --- a/src/smt/theory_arith_nl.h +++ b/src/smt/theory_arith_nl.h @@ -80,14 +80,12 @@ void theory_arith::mark_dependents(theory_var v, svector & vars if (is_fixed(v)) return; column & c = m_columns[v]; - typename svector::iterator it = c.begin_entries(); - typename svector::iterator end = c.end_entries(); - for (; it != end; ++it) { - if (it->is_dead() || already_visited_rows.contains(it->m_row_id)) + for (auto& ce : c) { + if (ce.is_dead() || already_visited_rows.contains(ce.m_row_id)) continue; - TRACE("non_linear_bug", tout << "visiting row: " << it->m_row_id << "\n";); - already_visited_rows.insert(it->m_row_id); - row & r = m_rows[it->m_row_id]; + TRACE("non_linear_bug", tout << "visiting row: " << ce.m_row_id << "\n";); + already_visited_rows.insert(ce.m_row_id); + row & r = m_rows[ce.m_row_id]; theory_var s = r.get_base_var(); // ignore quasi base vars... actually they should not be used if the problem is non linear... if (is_quasi_base(s)) @@ -97,14 +95,10 @@ void theory_arith::mark_dependents(theory_var v, svector & vars // was eliminated by substitution. if (s != null_theory_var && is_free(s) && s != v) continue; - typename vector::const_iterator it2 = r.begin_entries(); - typename vector::const_iterator end2 = r.end_entries(); - for (; it2 != end2; ++it2) { - if (!it2->is_dead() && !is_fixed(it2->m_var)) - mark_var(it2->m_var, vars, already_found); - if (!it2->is_dead() && is_fixed(it2->m_var)) { - TRACE("non_linear", tout << "skipped fixed\n";); - } + for (auto& re : r) { + if (!re.is_dead() && !is_fixed(re.m_var)) + mark_var(re.m_var, vars, already_found); + CTRACE("non_linear", !re.is_dead() && is_fixed(re.m_var), tout << "skipped fixed\n"); } } } @@ -119,6 +113,7 @@ void theory_arith::get_non_linear_cluster(svector & vars) { return; var_set already_found; row_set already_visited_rows; + for (theory_var v : m_nl_monomials) { expr * n = var2expr(v); if (ctx.is_relevant(n)) @@ -130,9 +125,9 @@ void theory_arith::get_non_linear_cluster(svector & vars) { TRACE("non_linear", tout << "marking dependents of: v" << v << "\n";); mark_dependents(v, vars, already_found, already_visited_rows); } - TRACE("non_linear", tout << "variables in non linear cluster:\n"; - for (theory_var v : vars) tout << "v" << v << " "; - tout << "\n";); + TRACE("non_linear", tout << "variables in non linear cluster: "; + for (theory_var v : vars) tout << "v" << v << " "; tout << "\n"; + for (theory_var v : m_nl_monomials) tout << "non-linear v" << v << " " << mk_pp(var2expr(v), m) << "\n";); } @@ -1740,22 +1735,21 @@ grobner::monomial * theory_arith::mk_gb_monomial(rational const & _coeff, e */ template void theory_arith::add_row_to_gb(row const & r, grobner & gb) { - TRACE("non_linear", tout << "adding row to gb\n"; display_row(tout, r);); + TRACE("grobner", tout << "adding row to gb\n"; display_row(tout, r);); ptr_buffer monomials; v_dependency * dep = nullptr; m_tmp_var_set.reset(); - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - rational coeff = it->m_coeff.to_rational(); - expr * m = var2expr(it->m_var); - TRACE("non_linear", tout << "monomial: " << mk_pp(m, get_manager()) << "\n";); - grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set); - TRACE("non_linear", tout << "new monomial:\n"; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";); - if (new_m) - monomials.push_back(new_m); - } + for (auto& re : r) { + if (re.is_dead()) + continue; + rational coeff = re.m_coeff.to_rational(); + expr * m = var2expr(re.m_var); + grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set); + if (new_m) + monomials.push_back(new_m); + TRACE("grobner", + tout << "monomial: " << mk_pp(m, get_manager()) << "\n"; + tout << "new monomial: "; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";); } gb.assert_eq_0(monomials.size(), monomials.data(), dep); } @@ -2158,8 +2152,9 @@ bool theory_arith::get_gb_eqs_and_look_for_conflict(ptr_vector _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); }; for (grobner::equation* eq : eqs) - gb.display_equation(tout, *eq); + gb.display_equation(tout, *eq, _fn); ); for (grobner::equation* eq : eqs) { if (is_inconsistent(eq, gb) || is_inconsistent2(eq, gb)) { @@ -2259,7 +2254,9 @@ typename theory_arith::gb_result theory_arith::compute_grobner(svector ptr_vector eqs; do { - TRACE("non_linear_gb", tout << "before:\n"; gb.display(tout);); + TRACE("grobner", tout << "before grobner:\n"; + std::function _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); }; + gb.display(tout, _fn)); compute_basis(gb, warn); update_statistics(gb); TRACE("non_linear_gb", tout << "after:\n"; gb.display(tout);); diff --git a/src/smt/theory_array_base.cpp b/src/smt/theory_array_base.cpp index f6d4306a5..d1a01c807 100644 --- a/src/smt/theory_array_base.cpp +++ b/src/smt/theory_array_base.cpp @@ -101,7 +101,7 @@ namespace smt { SASSERT(num_args >= 3); sel_args.push_back(n); for (unsigned i = 1; i < num_args - 1; ++i) { - sel_args.push_back(to_app(n->get_arg(i))); + sel_args.push_back(n->get_arg(i)); } expr_ref sel(m); sel = mk_select(sel_args.size(), sel_args.data()); diff --git a/src/smt/theory_array_full.cpp b/src/smt/theory_array_full.cpp index 1a793a116..4311ef32c 100644 --- a/src/smt/theory_array_full.cpp +++ b/src/smt/theory_array_full.cpp @@ -594,10 +594,13 @@ namespace smt { if (!ctx.add_fingerprint(this, m_default_lambda_fingerprint, 1, &arr)) return false; m_stats.m_num_default_lambda_axiom++; - expr* def = mk_default(arr->get_expr()); + expr* e = arr->get_expr(); + expr* def = mk_default(e); quantifier* lam = m.is_lambda_def(arr->get_decl()); - expr_ref_vector args(m); - args.push_back(lam); + TRACE("array", tout << mk_pp(lam, m) << "\n" << mk_pp(e, m) << "\n"); + expr_ref_vector args(m); + var_subst subst(m, false); + args.push_back(subst(lam, to_app(e)->get_num_args(), to_app(e)->get_args())); for (unsigned i = 0; i < lam->get_num_decls(); ++i) args.push_back(mk_epsilon(lam->get_decl_sort(i)).first); expr_ref val(mk_select(args), m); diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index 90c8cd89e..196f49281 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -52,9 +52,8 @@ namespace smt { bits.reset(); m_bits_expr.reset(); - for (unsigned i = 0; i < bv_size; i++) { + for (unsigned i = 0; i < bv_size; i++) m_bits_expr.push_back(mk_bit2bool(owner, i)); - } ctx.internalize(m_bits_expr.data(), bv_size, true); for (unsigned i = 0; i < bv_size; i++) { @@ -601,9 +600,8 @@ namespace smt { TRACE("bv", tout << mk_bounded_pp(n, m) << "\n";); process_args(n); mk_enode(n); - if (!ctx.relevancy()) { + if (!ctx.relevancy()) assert_bv2int_axiom(n); - } } @@ -669,10 +667,12 @@ namespace smt { mk_enode(n); theory_var v = ctx.get_enode(n)->get_th_var(get_id()); mk_bits(v); - - if (!ctx.relevancy()) { + enode* k = ctx.get_enode(n->get_arg(0)); + if (!is_attached_to_var(k)) + mk_var(k); + + if (!ctx.relevancy()) assert_int2bv_axiom(n); - } } void theory_bv::assert_int2bv_axiom(app* n) { @@ -1497,6 +1497,26 @@ namespace smt { unsigned sz = m_bits[v1].size(); bool changed = true; TRACE("bv", tout << "bits size: " << sz << "\n";); + if (sz == 0) { + // int2bv(bv2int(x)) = x when int2bv(bv2int(x)) has same sort as x + enode* n1 = get_enode(r1); + for (enode* bv2int : *n1) { + if (!m_util.is_bv2int(bv2int->get_expr())) + continue; + enode* bv2int_arg = bv2int->get_arg(0); + for (enode* p : enode::parents(n1->get_root())) { + if (m_util.is_int2bv(p->get_expr()) && p->get_root() != bv2int_arg->get_root() && p->get_sort() == bv2int_arg->get_sort()) { + enode_pair_vector eqs; + eqs.push_back({n1, p->get_arg(0) }); + eqs.push_back({n1, bv2int}); + justification * js = ctx.mk_justification( + ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), 0, nullptr, eqs.size(), eqs.data(), p, bv2int_arg)); + ctx.assign_eq(p, bv2int_arg, eq_justification(js)); + break; + } + } + } + } do { // This outerloop is necessary to avoid missing propagation steps. // For example, let's assume that bits1 and bits2 contains the following diff --git a/src/smt/theory_datatype.cpp b/src/smt/theory_datatype.cpp index 27b922dba..d11172691 100644 --- a/src/smt/theory_datatype.cpp +++ b/src/smt/theory_datatype.cpp @@ -25,6 +25,7 @@ Revision History: #include "smt/theory_datatype.h" #include "smt/theory_array.h" #include "smt/smt_model_generator.h" +#include namespace smt { @@ -519,9 +520,8 @@ namespace smt { void theory_datatype::explain_is_child(enode* parent, enode* child) { enode * parentc = oc_get_cstor(parent); - if (parent != parentc) { + if (parent != parentc) m_used_eqs.push_back(enode_pair(parent, parentc)); - } // collect equalities on all children that may have been used. bool found = false; @@ -546,14 +546,17 @@ namespace smt { } sort* se = nullptr; if (m_sutil.is_seq(s, se) && m_util.is_datatype(se)) { - for (enode* aarg : get_seq_args(arg)) { + enode* sibling; + for (enode* aarg : get_seq_args(arg, sibling)) { if (aarg->get_root() == child->get_root()) { - if (aarg != child) { + if (aarg != child) m_used_eqs.push_back(enode_pair(aarg, child)); - } found = true; } } + if (sibling && sibling != arg) + m_used_eqs.push_back(enode_pair(arg, sibling)); + } } VERIFY(found); @@ -582,9 +585,11 @@ namespace smt { TRACE("datatype", tout << "occurs_check\n"; - for (enode_pair const& p : m_used_eqs) { + for (enode_pair const& p : m_used_eqs) tout << enode_eq_pp(p, ctx); - }); + for (auto const& [a,b] : m_used_eqs) + tout << mk_pp(a->get_expr(), m) << " = " << mk_pp(b->get_expr(), m) << "\n"; + ); } // start exploring subgraph below `app` @@ -596,9 +601,9 @@ namespace smt { } v = m_find.find(v); var_data * d = m_var_data[v]; - if (!d->m_constructor) { + + if (!d->m_constructor) return false; - } enode * parent = d->m_constructor; oc_mark_on_stack(parent); auto process_arg = [&](enode* aarg) { @@ -616,9 +621,8 @@ namespace smt { }; for (enode * arg : enode::args(parent)) { - if (oc_cycle_free(arg)) { + if (oc_cycle_free(arg)) continue; - } if (oc_on_stack(arg)) { // arg was explored before app, and is still on the stack: cycle occurs_check_explain(parent, arg); @@ -632,9 +636,11 @@ namespace smt { oc_push_stack(arg); } else if (m_sutil.is_seq(s, se) && m_util.is_datatype(se)) { - for (enode* sarg : get_seq_args(arg)) - if (process_arg(sarg)) + enode* sibling; + for (enode* sarg : get_seq_args(arg, sibling)) { + if (process_arg(sarg)) return true; + } } else if (m_autil.is_array(s) && m_util.is_datatype(get_array_range(s))) { for (enode* aarg : get_array_args(arg)) @@ -645,7 +651,7 @@ namespace smt { return false; } - ptr_vector const& theory_datatype::get_seq_args(enode* n) { + ptr_vector const& theory_datatype::get_seq_args(enode* n, enode*& sibling) { m_args.reset(); m_todo.reset(); auto add_todo = [&](enode* n) { @@ -654,9 +660,14 @@ namespace smt { m_todo.push_back(n); } }; - - for (enode* sib : *n) - add_todo(sib); + + for (enode* sib : *n) { + if (m_sutil.str.is_concat_of_units(sib->get_expr())) { + add_todo(sib); + sibling = sib; + break; + } + } for (unsigned i = 0; i < m_todo.size(); ++i) { enode* n = m_todo[i]; @@ -691,7 +702,7 @@ namespace smt { a3 = cons(v3, a1) */ bool theory_datatype::occurs_check(enode * n) { - TRACE("datatype", tout << "occurs check: " << enode_pp(n, ctx) << "\n";); + TRACE("datatype_verbose", tout << "occurs check: " << enode_pp(n, ctx) << "\n";); m_stats.m_occurs_check++; bool res = false; @@ -706,7 +717,7 @@ namespace smt { if (oc_cycle_free(app)) continue; - TRACE("datatype", tout << "occurs check loop: " << enode_pp(app, ctx) << (op==ENTER?" enter":" exit")<< "\n";); + TRACE("datatype_verbose", tout << "occurs check loop: " << enode_pp(app, ctx) << (op==ENTER?" enter":" exit")<< "\n";); switch (op) { case ENTER: @@ -830,15 +841,14 @@ namespace smt { SASSERT(d->m_constructor); func_decl * c_decl = d->m_constructor->get_decl(); datatype_value_proc * result = alloc(datatype_value_proc, c_decl); - for (enode* arg : enode::args(d->m_constructor)) { + for (enode* arg : enode::args(d->m_constructor)) result->add_dependency(arg); - } TRACE("datatype", tout << pp(n, m) << "\n"; tout << "depends on\n"; - for (enode* arg : enode::args(d->m_constructor)) { + for (enode* arg : enode::args(d->m_constructor)) tout << " " << pp(arg, m) << "\n"; - }); + ); return result; } @@ -965,12 +975,11 @@ namespace smt { SASSERT(!lits.empty()); region & reg = ctx.get_region(); TRACE("datatype_conflict", tout << mk_ismt2_pp(recognizer->get_expr(), m) << "\n"; - for (literal l : lits) { + for (literal l : lits) ctx.display_detailed_literal(tout, l) << "\n"; - } - for (auto const& p : eqs) { + for (auto const& p : eqs) tout << enode_eq_pp(p, ctx); - }); + ); ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), reg, lits.size(), lits.data(), eqs.size(), eqs.data()))); } else if (num_unassigned == 1) { @@ -1052,9 +1061,8 @@ namespace smt { ctx.mark_as_relevant(curr); return; } - else if (ctx.get_assignment(curr) != l_false) { + else if (ctx.get_assignment(curr) != l_false) return; - } } if (r == nullptr) return; // all recognizers are asserted to false... conflict will be detected... diff --git a/src/smt/theory_datatype.h b/src/smt/theory_datatype.h index c0e06b58d..d64cc9388 100644 --- a/src/smt/theory_datatype.h +++ b/src/smt/theory_datatype.h @@ -94,7 +94,7 @@ namespace smt { void oc_push_stack(enode * n); ptr_vector m_args, m_todo; ptr_vector const& get_array_args(enode* n); - ptr_vector const& get_seq_args(enode* n); + ptr_vector const& get_seq_args(enode* n, enode*& sibling); // class for managing state of final_check class final_check_st { diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 67d9988ed..75f9cb997 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -276,23 +276,23 @@ class theory_lra::imp { m_nla->push(); } smt_params_helper prms(ctx().get_params()); - m_nla->settings().run_order() = prms.arith_nl_order(); - m_nla->settings().run_tangents() = prms.arith_nl_tangents(); - m_nla->settings().run_horner() = prms.arith_nl_horner(); - m_nla->settings().horner_subs_fixed() = prms.arith_nl_horner_subs_fixed(); - m_nla->settings().horner_frequency() = prms.arith_nl_horner_frequency(); - m_nla->settings().horner_row_length_limit() = prms.arith_nl_horner_row_length_limit(); - m_nla->settings().run_grobner() = prms.arith_nl_grobner(); - m_nla->settings().run_nra() = prms.arith_nl_nra(); - m_nla->settings().grobner_subs_fixed() = prms.arith_nl_grobner_subs_fixed(); - m_nla->settings().grobner_eqs_growth() = prms.arith_nl_grobner_eqs_growth(); - m_nla->settings().grobner_expr_size_growth() = prms.arith_nl_grobner_expr_size_growth(); - m_nla->settings().grobner_expr_degree_growth() = prms.arith_nl_grobner_expr_degree_growth(); - m_nla->settings().grobner_max_simplified() = prms.arith_nl_grobner_max_simplified(); - m_nla->settings().grobner_number_of_conflicts_to_report() = prms.arith_nl_grobner_cnfl_to_report(); - m_nla->settings().grobner_quota() = prms.arith_nl_gr_q(); - m_nla->settings().grobner_frequency() = prms.arith_nl_grobner_frequency(); - m_nla->settings().expensive_patching() = false; + m_nla->settings().run_order = prms.arith_nl_order(); + m_nla->settings().run_tangents = prms.arith_nl_tangents(); + m_nla->settings().run_horner = prms.arith_nl_horner(); + m_nla->settings().horner_subs_fixed = prms.arith_nl_horner_subs_fixed(); + m_nla->settings().horner_frequency = prms.arith_nl_horner_frequency(); + m_nla->settings().horner_row_length_limit = prms.arith_nl_horner_row_length_limit(); + m_nla->settings().run_grobner = prms.arith_nl_grobner(); + m_nla->settings().run_nra = prms.arith_nl_nra(); + m_nla->settings().grobner_subs_fixed = prms.arith_nl_grobner_subs_fixed(); + m_nla->settings().grobner_eqs_growth = prms.arith_nl_grobner_eqs_growth(); + m_nla->settings().grobner_expr_size_growth = prms.arith_nl_grobner_expr_size_growth(); + m_nla->settings().grobner_expr_degree_growth = prms.arith_nl_grobner_expr_degree_growth(); + m_nla->settings().grobner_max_simplified = prms.arith_nl_grobner_max_simplified(); + m_nla->settings().grobner_number_of_conflicts_to_report = prms.arith_nl_grobner_cnfl_to_report(); + m_nla->settings().grobner_quota = prms.arith_nl_gr_q(); + m_nla->settings().grobner_frequency = prms.arith_nl_grobner_frequency(); + m_nla->settings().expensive_patching = false; } } @@ -1224,9 +1224,9 @@ public: return; } expr_ref mod_r(a.mk_add(a.mk_mul(q, div), mod), m); - + ctx().get_rewriter()(mod_r); expr_ref eq_r(th.mk_eq_atom(mod_r, p), m); - ctx().internalize(eq_r, false); + ctx().internalize(eq_r, false); literal eq = ctx().get_literal(eq_r); rational k(0); @@ -1256,6 +1256,39 @@ public: } else { + expr_ref abs_q(m.mk_ite(a.mk_ge(q, zero), q, a.mk_uminus(q)), m); + expr_ref mone(a.mk_int(-1), m); + expr_ref modmq(a.mk_sub(mod, abs_q), m); + ctx().get_rewriter()(modmq); + literal eqz = mk_literal(m.mk_eq(q, zero)); + literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); + literal mod_lt_q = mk_literal(a.mk_le(modmq, mone)); + + // q = 0 or p = (p mod q) + q * (p div q) + // q = 0 or (p mod q) >= 0 + // q = 0 or (p mod q) < abs(q) + + mk_axiom(eqz, eq); + mk_axiom(eqz, mod_ge_0); + mk_axiom(eqz, mod_lt_q); + m_arith_eq_adapter.mk_axioms(th.ensure_enode(mod_r), th.ensure_enode(p)); + + if (a.is_zero(p)) { + mk_axiom(eqz, mk_literal(m.mk_eq(div, zero))); + mk_axiom(eqz, mk_literal(m.mk_eq(mod, zero))); + } + // (or (= y 0) (<= (* y (div x y)) x)) + else if (!a.is_numeral(q)) { + expr_ref div_ge(m); + div_ge = a.mk_ge(a.mk_sub(p, a.mk_mul(q, div)), zero); + ctx().get_rewriter()(div_ge); + mk_axiom(eqz, mk_literal(div_ge)); + TRACE("arith", tout << eqz << " " << div_ge << "\n"); + } + + +#if 0 + /*literal div_ge_0 = */ mk_literal(a.mk_ge(div, zero)); /*literal div_le_0 = */ mk_literal(a.mk_le(div, zero)); /*literal p_ge_0 = */ mk_literal(a.mk_ge(p, zero)); @@ -1266,7 +1299,7 @@ public: // q >= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) < q - // q >= 0 or (p mod q) < -q + // q >= 0 or (p mod q) < -q literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); literal q_le_0 = mk_literal(a.mk_le(q, zero)); literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); @@ -1277,11 +1310,11 @@ public: mk_axiom(q_le_0, mod_ge_0); mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); - +#endif #if 0 // seem expensive - + mk_axiom(q_le_0, ~p_ge_0, div_ge_0); mk_axiom(q_le_0, ~p_le_0, div_le_0); mk_axiom(q_ge_0, ~p_ge_0, div_le_0); @@ -1293,19 +1326,21 @@ public: mk_axiom(q_ge_0, p_le_0, ~div_ge_0); #endif +#if 0 std::function log = [&,this]() { th.log_axiom_unit(m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(eq.var()))); th.log_axiom_unit(m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(mod_ge_0.var()))); th.log_axiom_unit(m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_sub(mod, q), zero))); th.log_axiom_unit(m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_add(mod, q), zero))); + }; + if_trace_stream _ts(m, log); +#endif #if 0 th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_ge_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_le_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_le_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_ge_0.var()))); #endif - }; - if_trace_stream _ts(m, log); } if (params().m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { unsigned _k = k.get_unsigned(); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index c18e5577b..416275275 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -43,32 +43,32 @@ namespace smt { char const * theory_recfun::get_name() const { return "recfun"; } - theory* theory_recfun::mk_fresh(context* new_ctx) { + theory* theory_recfun::mk_fresh(context* new_ctx) { return alloc(theory_recfun, *new_ctx); } bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { + TRACE("recfun", tout << mk_pp(atom, m) << " " << u().has_defs() << "\n"); if (!u().has_defs()) { +// if (u().is_defined(atom)) +// throw default_exception("recursive atom definition is out of scope"); return false; } - for (expr * arg : *atom) { + for (expr * arg : *atom) ctx.internalize(arg, false); - } - if (!ctx.e_internalized(atom)) { + if (!ctx.e_internalized(atom)) ctx.mk_enode(atom, false, true, true); - } - if (!ctx.b_internalized(atom)) { - bool_var v = ctx.mk_bool_var(atom); - ctx.set_var_theory(v, get_id()); - } - if (!ctx.relevancy() && u().is_defined(atom)) { + if (!ctx.b_internalized(atom)) + ctx.set_var_theory(ctx.mk_bool_var(atom), get_id()); + if (!ctx.relevancy() && u().is_defined(atom)) push_case_expand(atom); - } return true; } bool theory_recfun::internalize_term(app * term) { if (!u().has_defs()) { +// if (u().is_defined(term)) +// throw default_exception("recursive term definition is out of scope"); return false; } for (expr* e : *term) { diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 3b801256f..9dc0b8740 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -2783,26 +2783,25 @@ bool theory_seq::get_length(expr* e, rational& val) { todo.push_back(e1); todo.push_back(e2); } - else if (m_util.str.is_unit(c)) { + else if (m_util.str.is_unit(c)) val += rational(1); - } - else if (m_util.str.is_empty(c)) { + else if (m_util.str.is_empty(c)) continue; - } - else if (m_util.str.is_string(c, s)) { + else if (m_util.str.is_map(c, e1, e2)) + todo.push_back(e2); + else if (m_util.str.is_mapi(c, e1, e2, c)) + todo.push_back(c); + else if (m_util.str.is_string(c, s)) val += rational(s.length()); - } - else if (!has_length(c)) { - len = mk_len(c); - add_axiom(mk_literal(m_autil.mk_ge(len, m_autil.mk_int(0)))); - TRACE("seq", tout << "literal has no length " << mk_pp(c, m) << "\n";); - return false; - } else { len = mk_len(c); - if (m_arith_value.get_value(len, val1) && !val1.is_neg()) { - val += val1; + if (!has_length(c)) { + add_axiom(mk_literal(m_autil.mk_ge(len, m_autil.mk_int(0)))); + TRACE("seq", tout << "literal has no length " << mk_pp(c, m) << "\n";); + return false; } + else if (m_arith_value.get_value(len, val1) && !val1.is_neg()) + val += val1; else { TRACE("seq", tout << "length has not been internalized " << mk_pp(c, m) << "\n";); return false; @@ -3071,9 +3070,13 @@ void theory_seq::assign_eh(bool_var v, bool is_true) { } else if (m_util.str.is_is_digit(e)) { + } + else if (m_util.str.is_foldl(e) || m_util.str.is_foldli(e)) { + } else { TRACE("seq", tout << mk_pp(e, m) << "\n";); + IF_VERBOSE(0, verbose_stream() << mk_pp(e, m) << "\n"); UNREACHABLE(); } } @@ -3216,18 +3219,15 @@ void theory_seq::relevant_eh(app* n) { add_ubv_string(n); expr* arg = nullptr; - if (m_sk.is_tail(n, arg)) { + if (m_sk.is_tail(n, arg)) add_length_limit(arg, m_max_unfolding_depth, true); - } - if (m_util.str.is_length(n, arg) && !has_length(arg) && ctx.e_internalized(arg)) { + if (m_util.str.is_length(n, arg) && !has_length(arg) && ctx.e_internalized(arg)) add_length_to_eqc(arg); - } if (m_util.str.is_replace_all(n) || m_util.str.is_replace_re(n) || - m_util.str.is_replace_re_all(n) - ) { + m_util.str.is_replace_re_all(n)) { add_unhandled_expr(n); } } diff --git a/src/solver/assertions/asserted_formulas.cpp b/src/solver/assertions/asserted_formulas.cpp index c6a8093ff..e1345fe92 100644 --- a/src/solver/assertions/asserted_formulas.cpp +++ b/src/solver/assertions/asserted_formulas.cpp @@ -154,6 +154,7 @@ void asserted_formulas::assert_expr(expr * e, proof * _in_pr) { force_push(); proof_ref in_pr(_in_pr, m), pr(_in_pr, m); expr_ref r(e, m); + SASSERT(m.is_bool(e)); if (inconsistent()) return; diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index 2f3ba7798..1d24ed1e6 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -39,10 +39,26 @@ Notes: #include "solver/parallel_tactic.h" #include "solver/parallel_params.hpp" + +class non_parallel_tactic : public tactic { +public: + non_parallel_tactic(solver* s, params_ref const& p) { + } + + char const* name() const override { return "parallel_tactic"; } + + void operator()(const goal_ref & g,goal_ref_buffer & result) override { + throw default_exception("parallel tactic is disabled in single threaded mode"); + } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} + +}; + #ifdef SINGLE_THREAD tactic * mk_parallel_tactic(solver* s, params_ref const& p) { - throw default_exception("parallel tactic is disabled in single threaded mode"); + return alloc(non_parallel_tactic, s, p); } #else @@ -97,9 +113,9 @@ class parallel_tactic : public tactic { void shutdown() { if (!m_shutdown) { + std::lock_guard lock(m_mutex); m_shutdown = true; m_cond.notify_all(); - std::lock_guard lock(m_mutex); for (solver_state* st : m_active) { st->m().limit().cancel(); } @@ -131,7 +147,9 @@ class parallel_tactic : public tactic { } { std::unique_lock lock(m_mutex); - m_cond.wait(lock); + if (!m_shutdown) { + m_cond.wait(lock); + } } dec_wait(); } diff --git a/src/solver/tactic2solver.cpp b/src/solver/tactic2solver.cpp index fe89d6533..09ede0c35 100644 --- a/src/solver/tactic2solver.cpp +++ b/src/solver/tactic2solver.cpp @@ -116,8 +116,8 @@ public: m_tactic->user_propagate_register_created(created_eh); } - void user_propagate_register_decide(user_propagator::decide_eh_t& created_eh) override { - m_tactic->user_propagate_register_decide(created_eh); + void user_propagate_register_decide(user_propagator::decide_eh_t& decide_eh) override { + m_tactic->user_propagate_register_decide(decide_eh); } void user_propagate_clear() override { diff --git a/src/tactic/bv/bv_size_reduction_tactic.cpp b/src/tactic/bv/bv_size_reduction_tactic.cpp index a9d15a2a6..18083452b 100644 --- a/src/tactic/bv/bv_size_reduction_tactic.cpp +++ b/src/tactic/bv/bv_size_reduction_tactic.cpp @@ -110,7 +110,28 @@ public: unsigned sz = g.size(); numeral val; unsigned bv_sz; - expr * f, * lhs, * rhs; + expr * f, * lhs, * rhs; + + auto match_bitmask = [&](expr* lhs, expr* rhs) { + unsigned lo, hi; + expr* arg; + if (!m_util.is_numeral(rhs, val, bv_sz)) + return false; + if (!val.is_zero()) + return false; + if (!m_util.is_extract(lhs, lo, hi, arg)) + return false; + if (lo == 0) + return false; + if (hi + 1 != m_util.get_bv_size(arg)) + return false; + if (!is_uninterp_const(arg)) + return false; + val = rational::power_of_two(lo - 1) -1 ; + update_unsigned_upper(to_app(arg), val); + return true; + }; + for (unsigned i = 0; i < sz; i++) { bool negated = false; f = g.form(i); @@ -152,22 +173,31 @@ public: else update_signed_lower(to_app(rhs), val); } } - -#if 0 +#if 0 else if (m_util.is_bv_ule(f, lhs, rhs)) { if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, val, bv_sz)) { TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); // v <= k - if (negated) update_unsigned_lower(to_app(lhs), val+numeral(1)); - else update_unsigned_upper(to_app(lhs), val); + if (negated) + update_unsigned_lower(to_app(lhs), val+numeral(1)); + else + update_unsigned_upper(to_app(lhs), val); } else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, val, bv_sz)) { TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); // k <= v - if (negated) update_unsigned_upper(to_app(rhs), val-numeral(1)); - else update_unsigned_lower(to_app(rhs), val); + if (negated) + update_unsigned_upper(to_app(rhs), val-numeral(1)); + else + update_unsigned_lower(to_app(rhs), val); } } + else if (m.is_eq(f, lhs, rhs)) { + if (match_bitmask(lhs, rhs)) + continue; + if (match_bitmask(rhs, lhs)) + continue; + } #endif } } @@ -185,33 +215,48 @@ public: mc = nullptr; m_mc = nullptr; unsigned num_reduced = 0; + { tactic_report report("reduce-bv-size", g); collect_bounds(g); // create substitution expr_substitution subst(m); + + auto insert_def = [&](app* k, expr* new_def, app* new_const) { + if (!new_def) + return; + subst.insert(k, new_def); + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(bv_size_reduction_mc, m, "bv_size_reduction"); + m_mc->add(k, new_def); + if (!m_fmc && new_const) + m_fmc = alloc(generic_model_converter, m, "bv_size_reduction"); + if (new_const) + m_fmc->hide(new_const); + } + num_reduced++; + }; + if (!(m_signed_lowers.empty() || m_signed_uppers.empty())) { TRACE("bv_size_reduction", - tout << "m_signed_lowers: " << std::endl; - for (obj_map::iterator it = m_signed_lowers.begin(); it != m_signed_lowers.end(); it++) - tout << mk_ismt2_pp(it->m_key, m) << " >= " << it->m_value.to_string() << std::endl; - tout << "m_signed_uppers: " << std::endl; - for (obj_map::iterator it = m_signed_uppers.begin(); it != m_signed_uppers.end(); it++) - tout << mk_ismt2_pp(it->m_key, m) << " <= " << it->m_value.to_string() << std::endl; - ); + tout << "m_signed_lowers: " << std::endl; + for (auto const& [k, v] : m_signed_lowers) + tout << mk_ismt2_pp(k, m) << " >= " << v.to_string() << std::endl; + tout << "m_signed_uppers: " << std::endl; + for (auto const& [k, v] : m_signed_uppers) + tout << mk_ismt2_pp(k, m) << " <= " << v.to_string() << std::endl; + ); - obj_map::iterator it = m_signed_lowers.begin(); - obj_map::iterator end = m_signed_lowers.end(); - for (; it != end; ++it) { - app * v = it->m_key; - unsigned bv_sz = m_util.get_bv_size(v); - numeral l = m_util.norm(it->m_value, bv_sz, true); - obj_map::obj_map_entry * entry = m_signed_uppers.find_core(v); + for (auto& [k, val] : m_signed_lowers) { + unsigned bv_sz = m_util.get_bv_size(k); + numeral l = m_util.norm(val, bv_sz, true); + obj_map::obj_map_entry * entry = m_signed_uppers.find_core(k); if (entry != nullptr) { numeral u = m_util.norm(entry->get_data().m_value, bv_sz, true); - TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << "\n";); + TRACE("bv_size_reduction", tout << l << " <= " << k->get_decl()->get_name() << " <= " << u << "\n";); expr * new_def = nullptr; app * new_const = nullptr; if (l > u) { @@ -219,19 +264,19 @@ public: return; } else if (l == u) { - new_def = m_util.mk_numeral(l, v->get_sort()); + new_def = m_util.mk_numeral(l, k->get_sort()); } else { // l < u if (l.is_neg()) { unsigned l_nb = (-l).get_num_bits(); - unsigned v_nb = m_util.get_bv_size(v); + unsigned v_nb = m_util.get_bv_size(k); if (u.is_neg()) { // l <= v <= u <= 0 unsigned i_nb = l_nb; - TRACE("bv_size_reduction", tout << " l <= " << v->get_decl()->get_name() << " <= u <= 0 " << " --> " << i_nb << " bits\n";); + TRACE("bv_size_reduction", tout << " l <= " << k->get_decl()->get_name() << " <= u <= 0 " << " --> " << i_nb << " bits\n";); if (i_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(i_nb)); new_def = m_util.mk_concat(m_util.mk_numeral(numeral(-1), v_nb - i_nb), new_const); @@ -241,7 +286,7 @@ public: // l <= v <= 0 <= u unsigned u_nb = u.get_num_bits(); unsigned i_nb = ((l_nb > u_nb) ? l_nb : u_nb) + 1; - TRACE("bv_size_reduction", tout << " l <= " << v->get_decl()->get_name() << " <= 0 <= u " << " --> " << i_nb << " bits\n";); + TRACE("bv_size_reduction", tout << " l <= " << k->get_decl()->get_name() << " <= 0 <= u " << " --> " << i_nb << " bits\n";); if (i_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(i_nb)); new_def = m_util.mk_sign_extend(v_nb - i_nb, new_const); @@ -251,31 +296,30 @@ public: else { // 0 <= l <= v <= u unsigned u_nb = u.get_num_bits(); - unsigned v_nb = m_util.get_bv_size(v); - TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << " --> " << u_nb << " bits\n";); + unsigned v_nb = m_util.get_bv_size(k); + TRACE("bv_size_reduction", tout << l << " <= " << k->get_decl()->get_name() << " <= " << u << " --> " << u_nb << " bits\n";); if (u_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(u_nb)); new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), v_nb - u_nb), new_const); } } } - - if (new_def) { - subst.insert(v, new_def); - if (m_produce_models) { - if (!m_mc) - m_mc = alloc(bv_size_reduction_mc, m, "bv_size_reduction"); - m_mc->add(v, new_def); - if (!m_fmc && new_const) - m_fmc = alloc(generic_model_converter, m, "bv_size_reduction"); - if (new_const) - m_fmc->hide(new_const); - } - num_reduced++; - } + + insert_def(k, new_def, new_const); } } } + + for (auto const& [k, v] : m_unsigned_uppers) { + unsigned shift; + unsigned bv_sz = m_util.get_bv_size(k); + numeral u = m_util.norm(v, bv_sz, true) + 1; + if (u.is_power_of_two(shift) && shift < bv_sz) { + app* new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(shift)); + expr* new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), bv_sz - shift), new_const); + insert_def(k, new_def, new_const); + } + } #if 0 if (!(m_unsigned_lowers.empty() && m_unsigned_uppers.empty())) { diff --git a/src/tactic/tactical.cpp b/src/tactic/tactical.cpp index 67a0e3062..25f8365e3 100644 --- a/src/tactic/tactical.cpp +++ b/src/tactic/tactical.cpp @@ -424,10 +424,20 @@ tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5 return or_else(10, ts); } +class no_par_tactical : public tactic { +public: + char const* name() const override { return "par"; } + void operator()(goal_ref const & in, goal_ref_buffer& result) override { + throw default_exception("par_tactical is unavailable in single threaded mode"); + } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} +}; + #ifdef SINGLE_THREAD tactic * par(unsigned num, tactic * const * ts) { - throw default_exception("par_tactical is unavailable in single threaded mode"); + return alloc(no_par_tactical); } #else @@ -576,11 +586,23 @@ tactic * par(tactic * t1, tactic * t2, tactic * t3, tactic * t4) { return par(4, ts); } +class no_par_and_then_tactical : public tactic { +public: + char const* name() const override { return "par_then"; } + void operator()(goal_ref const & in, goal_ref_buffer& result) override { + throw default_exception("par_and_then is not available in single threaded mode"); + } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} +}; + + #ifdef SINGLE_THREAD tactic * par_and_then(tactic * t1, tactic * t2) { - throw default_exception("par_and_then is not available in single threaded mode"); + return alloc(no_par_and_then_tactical); } + #else class par_and_then_tactical : public and_then_tactical { public: diff --git a/src/test/pdd.cpp b/src/test/pdd.cpp index 2b0f3f76a..f254a2e5c 100644 --- a/src/test/pdd.cpp +++ b/src/test/pdd.cpp @@ -593,6 +593,63 @@ public : SASSERT_EQ(coeff[2], 0); SASSERT_EQ(coeff[3], 3); } + + static void factors() { + pdd_manager m(3); + pdd v0 = m.mk_var(0); + pdd v1 = m.mk_var(1); + pdd v2 = m.mk_var(2); + pdd v3 = m.mk_var(3); + pdd v4 = m.mk_var(4); + pdd c1 = v0 * v1 * v2 + v2 * v0 + v1 + 1; + { + auto [vars, p] = c1.var_factors(); + VERIFY(p == c1 && vars.empty()); + } + { + auto q = c1 * v4; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 1 && vars[0] == 4); + } + for (unsigned i = 0; i < 5; ++i) { + auto v = m.mk_var(i); + auto q = c1 * v; + std::cout << i << ": " << q << "\n"; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 1 && vars[0] == i); + } + for (unsigned i = 0; i < 5; ++i) { + for (unsigned j = 0; j < 5; ++j) { + auto vi = m.mk_var(i); + auto vj = m.mk_var(j); + auto q = c1 * vi * vj; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 2); + VERIFY(vars[0] == i || vars[1] == i); + VERIFY(vars[0] == j || vars[1] == j); + } + } + for (unsigned i = 0; i < 5; ++i) { + for (unsigned j = i; j < 5; ++j) { + for (unsigned k = j; k < 5; ++k) { + auto vi = m.mk_var(i); + auto vj = m.mk_var(j); + auto vk = m.mk_var(k); + auto q = c1 * vi * vj * vk; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 3); + VERIFY(vars[0] == i || vars[1] == i || vars[2] == i); + VERIFY(vars[0] == j || vars[1] == j || vars[2] == j); + VERIFY(vars[0] == k || vars[1] == k || vars[2] == k); + } + } + } + } + }; } @@ -615,4 +672,5 @@ void tst_pdd() { dd::test::pow(); dd::test::subst_val(); dd::test::univariate(); + dd::test::factors(); } diff --git a/src/util/parray.h b/src/util/parray.h index 0c3173f6c..bc3c4adbe 100644 --- a/src/util/parray.h +++ b/src/util/parray.h @@ -91,7 +91,7 @@ private: } void dec_ref(unsigned sz, value * vs) { - if (C::ref_count) + if (C::ref_count) for (unsigned i = 0; i < sz; i++) m_vmanager.dec_ref(vs[i]); } @@ -151,7 +151,7 @@ private: size_t new_capacity = curr_capacity == 0 ? 2 : (3 * curr_capacity + 1) >> 1; value * new_vs = allocate_values(new_capacity); if (curr_capacity > 0) { - for (size_t i = 0; i < curr_capacity; i++) + for (size_t i = 0; i < curr_capacity; i++) new_vs[i] = vs[i]; deallocate_values(vs); } @@ -177,7 +177,7 @@ private: inc_ref(v); vs[sz] = v; sz++; - } + } void rpush_back(cell * c, value const & v) { SASSERT(c->kind() == ROOT); @@ -269,7 +269,7 @@ public: } value_manager & manager() { return m_vmanager; } - + void mk(ref & r) { dec_ref(r.m_ref); cell * new_c = mk(ROOT); @@ -283,12 +283,12 @@ public: r.m_ref = nullptr; r.m_updt_counter = 0; } - + void copy(ref const & s, ref & t) { inc_ref(s.m_ref); dec_ref(t.m_ref); t.m_ref = s.m_ref; - t.m_updt_counter = 0; + t.m_updt_counter = 0; } unsigned size(ref const & r) const { @@ -310,17 +310,15 @@ public: } void check_size(cell* c) const { - unsigned r; while (c) { switch (c->kind()) { case SET: break; case PUSH_BACK: - r = size(c->next()); + // ? SASSERT(c->idx() == size(c->next())); break; case POP_BACK: - r = size(c->next()); - SASSERT(c->idx() == r); + SASSERT(c->idx() == size(c->next())); break; case ROOT: return; @@ -333,7 +331,7 @@ public: value const & get(ref const & r, unsigned i) const { SASSERT(i < size(r)); - + unsigned trail_sz = 0; cell * c = r.m_ref; @@ -451,7 +449,7 @@ public: inc_ref(v); new_c->m_elem = v; new_c->m_next = r.m_ref; - r.m_ref = new_c; + r.m_ref = new_c; SASSERT(new_c->m_ref_count == 1); } @@ -536,7 +534,7 @@ public: r.m_updt_counter = 0; SASSERT(r.root()); } - + void reroot(ref & r) { if (r.root()) return; @@ -545,7 +543,7 @@ public: unsigned r_sz = size(r); unsigned trail_split_idx = r_sz / C::factor; unsigned i = 0; - cell * c = r.m_ref; + cell * c = r.m_ref; while (c->kind() != ROOT && i < trail_split_idx) { cs.push_back(c); c = c->next(); @@ -556,7 +554,7 @@ public: unfold(c); } DEBUG_CODE(check_size(c);); - SASSERT(c->kind() == ROOT); + SASSERT(c->kind() == ROOT); for (i = cs.size(); i-- > 0; ) { cell * p = cs[i]; SASSERT(c->m_kind == ROOT); @@ -574,7 +572,7 @@ public: case PUSH_BACK: c->m_kind = POP_BACK; if (sz == capacity(vs)) - expand(vs); + expand(vs); vs[sz] = p->m_elem; ++sz; c->m_idx = sz;