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 9b0d111e6..cffe1a4d7 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) +project(Z3 VERSION 4.9.2.0 LANGUAGES CXX) ################################################################################ # 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 94% rename from RELEASE_NOTES rename to RELEASE_NOTES.md index e9c5c1fd7..aecf10f2c 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ RELEASE NOTES -Version 4.8.next +Version 4.9.next ================ - Planned features - sat.euf @@ -10,6 +10,42 @@ 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.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 +53,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 +1023,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 +1045,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 +1077,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 +1144,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..c71c3ff2f 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; 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 ccc855734..9f7a69b56 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 0082bff42..1292646ac 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/ackermannization/lackr_model_constructor.cpp b/src/ackermannization/lackr_model_constructor.cpp index f9450771d..76c1803f2 100644 --- a/src/ackermannization/lackr_model_constructor.cpp +++ b/src/ackermannization/lackr_model_constructor.cpp @@ -21,6 +21,7 @@ #include "ast/for_each_expr.h" #include "ast/rewriter/bv_rewriter.h" #include "ast/rewriter/bool_rewriter.h" +#include struct lackr_model_constructor::imp { public: @@ -186,7 +187,7 @@ private: return m_app2val.find(a, val); } - bool evaluate(app * const a, expr_ref& result) { + bool evaluate(app * a, expr_ref& result) { SASSERT(!is_val(a)); const unsigned num = a->get_num_args(); if (num == 0) { // handle constants @@ -232,20 +233,20 @@ private: // Check and record the value for a given term, given that all arguments are already checked. // bool mk_value(app * a) { - if (is_val(a)) return true; // skip numerals + if (is_val(a)) + return true; // skip numerals TRACE("model_constructor", tout << "mk_value(\n" << mk_ismt2_pp(a, m, 2) << ")\n";); SASSERT(!m_app2val.contains(a)); expr_ref result(m); - if (!evaluate(a, result)) return false; - SASSERT(is_val(result)); + if (!evaluate(a, result)) + return false; TRACE("model_constructor", tout << "map term(\n" << mk_ismt2_pp(a, m, 2) << "\n->" << mk_ismt2_pp(result.get(), m, 2)<< ")\n"; ); CTRACE("model_constructor", !is_val(result.get()), - tout << "eval fail\n" << mk_ismt2_pp(a, m, 2) << mk_ismt2_pp(result, m, 2) << "\n"; + tout << "eval didn't create a constant \n" << mk_ismt2_pp(a, m, 2) << " " << mk_ismt2_pp(result, m, 2) << "\n"; ); - SASSERT(is_val(result.get())); m_app2val.insert(a, result.get()); // memoize m_pinned.push_back(a); m_pinned.push_back(result); 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..8aaeb31ab 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -2894,7 +2894,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++) 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 bfd7887dd..3e9344556 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -37,277 +37,326 @@ namespace Microsoft.Z3 /// /// Propagator context for .Net /// - public class UserPropagator + public class UserPropagator { /// /// 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); + public delegate void FixedEh(Expr term, Expr value); /// /// Delegate type for equality or disequality callback /// - public delegate void EqEh(Expr term, Expr value); + public delegate void EqEh(Expr term, Expr value); /// /// Delegate type for when a new term using a registered function symbol is created internally /// - public delegate void CreatedEh(Expr term); + public delegate void CreatedEh(Expr term); /// /// Delegate type for callback into solver's branching - /// A bit-vector or Boolean used for branching - /// If the term is a bit-vector, then an index into the bit-vector being branched on - /// Set phase to -1 (false) or 1 (true) to override solver's phase - /// - public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); + /// A bit-vector or Boolean used for branching + /// If the term is a bit-vector, then an index into the bit-vector being branched on + /// Set phase to -1 (false) or 1 (true) to override solver's phase + /// + public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); + + // access managed objects through a static array. + // thread safety is ignored for now. + GCHandle gch; + Solver solver; + Context ctx; + Z3_solver_callback callback = IntPtr.Zero; + FixedEh fixed_eh; + Action final_eh; + EqEh eq_eh; + EqEh diseq_eh; + CreatedEh created_eh; + DecideEh decide_eh; - Solver solver; - Context ctx; - GCHandle gch; - Z3_solver_callback callback = IntPtr.Zero; - FixedEh fixed_eh; - Action final_eh; - EqEh eq_eh; - EqEh diseq_eh; - CreatedEh created_eh; - DecideEh decide_eh; + Native.Z3_push_eh push_eh; + Native.Z3_pop_eh pop_eh; + Native.Z3_fresh_eh fresh_eh; - void Callback(Action fn, Z3_solver_callback cb) { - this.callback = cb; - try { - fn(); - } - catch { - // TBD: add debug log or exception handler - } - finally { - this.callback = IntPtr.Zero; - } - } + Native.Z3_fixed_eh fixed_wrapper; + Native.Z3_final_eh final_wrapper; + Native.Z3_eq_eh eq_wrapper; + Native.Z3_eq_eh diseq_wrapper; + Native.Z3_decide_eh decide_wrapper; + Native.Z3_created_eh created_wrapper; + + void Callback(Action fn, Z3_solver_callback cb) + { + this.callback = cb; + try + { + fn(); + } + catch + { + // TBD: add debug log or exception handler + } + finally + { + this.callback = IntPtr.Zero; + } + } - static void _push(voidp ctx, Z3_solver_callback cb) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; - prop.Callback(() => prop.Push(), cb); - } - - static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; - prop.Callback(() => prop.Pop(num_scopes), cb); - } - - static voidp _fresh(voidp _ctx, Z3_context new_context) { - var gch = GCHandle.FromIntPtr(_ctx); - var prop = (UserPropagator)gch.Target; - var ctx = new Context(new_context); - var prop1 = prop.Fresh(ctx); - return GCHandle.ToIntPtr(prop1.gch); - } + static void _push(voidp ctx, Z3_solver_callback cb) + { + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; + prop.Callback(() => prop.Push(), cb); + } - static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.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); - } + static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) + { + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; + prop.Callback(() => prop.Pop(num_scopes), cb); + } - static void _final(voidp ctx, Z3_solver_callback cb) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; - prop.Callback(() => prop.final_eh(), cb); - } + static voidp _fresh(voidp _ctx, Z3_context new_context) + { + var prop = (UserPropagator)GCHandle.FromIntPtr(_ctx).Target; + var ctx = new Context(new_context); + var prop1 = prop.Fresh(prop.ctx); + return GCHandle.ToIntPtr(prop1.gch); + } - static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.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); - } + static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) + { + 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); + } - static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.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); - } + static void _final(voidp ctx, Z3_solver_callback cb) + { + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; + prop.Callback(() => prop.final_eh(), cb); + } - static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; - using var t = Expr.Create(prop.ctx, a); - prop.Callback(() => prop.created_eh(t), cb); - } + static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) + { + 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); + } - static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; - var t = Expr.Create(prop.ctx, a); - var u = t; - prop.callback = cb; - prop.decide_eh(ref t, ref idx, ref phase); - prop.callback = IntPtr.Zero; - if (u != t) - a = t.NativeObject; - } + static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) + { + 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); + } + + static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) + { + 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 = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; + var t = Expr.Create(prop.ctx, a); + var u = t; + prop.callback = cb; + prop.decide_eh(ref t, ref idx, ref phase); + prop.callback = IntPtr.Zero; + if (u != t) + a = t.NativeObject; + } /// /// Propagator constructor from a solver class. /// public UserPropagator(Solver s) - { - gch = GCHandle.Alloc(this); - solver = s; - ctx = solver.Context; - var cb = GCHandle.ToIntPtr(gch); - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, cb, _push, _pop, _fresh); - } + { + 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, GCHandle.ToIntPtr(gch), push_eh, pop_eh, fresh_eh); + } /// /// Propagator constructor from a context. It is used from inside of Fresh. /// public UserPropagator(Context _ctx) - { - gch = GCHandle.Alloc(this); + { + gch = GCHandle.Alloc(this); solver = null; - ctx = _ctx; - } + ctx = _ctx; + } /// /// Release provate memory. /// - ~UserPropagator() - { - if (gch != null) - gch.Free(); + ~UserPropagator() + { + gch.Free(); if (solver == null) ctx.Dispose(); - } + } /// /// Virtual method for push. It must be overwritten by inherited class. - /// - public virtual void Push() { throw new Z3Exception("Push method should be overwritten"); } + /// + public virtual void Push() { throw new Z3Exception("Push method should be overwritten"); } /// /// Virtual method for pop. It must be overwritten by inherited class. - /// - public virtual void Pop(uint n) { throw new Z3Exception("Pop method should be overwritten"); } + /// + public virtual void Pop(uint n) { throw new Z3Exception("Pop method should be overwritten"); } /// /// Virtual method for fresh. It can be overwritten by inherited class. - /// - public virtual UserPropagator Fresh(Context ctx) { return new UserPropagator(ctx); } + /// + public virtual UserPropagator Fresh(Context ctx) { return new UserPropagator(ctx); } /// /// Declare combination of assigned expressions a conflict - /// - void Conflict(params Expr[] terms) { - Propagate(terms, ctx.MkFalse()); + /// + public void Conflict(params Expr[] terms) + { + Propagate(terms, ctx.MkFalse()); + } + + /// + /// Declare combination of assigned expressions a conflict + /// + public void Conflict(IEnumerable terms) + { + Propagate(terms, ctx.MkFalse()); } /// /// Propagate consequence - /// - void Propagate(Expr[] terms, Expr conseq) { - var nTerms = Z3Object.ArrayToNative(terms); - Native.Z3_solver_propagate_consequence(ctx.nCtx, this.callback, (uint)nTerms.Length, nTerms, 0u, null, null, conseq.NativeObject); + /// + public void Propagate(IEnumerable terms, Expr conseq) + { + var nTerms = Z3Object.ArrayToNative(terms.ToArray()); + Native.Z3_solver_propagate_consequence(ctx.nCtx, this.callback, (uint)nTerms.Length, nTerms, 0u, null, null, conseq.NativeObject); } /// /// Set fixed callback - /// + /// public FixedEh Fixed - { - set - { - this.fixed_eh = value; - if (solver != null) - Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, _fixed); - } - } + { + set + { + this.fixed_wrapper = _fixed; + this.fixed_eh = value; + if (solver != null) + Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, fixed_wrapper); + } + } /// /// Set final callback - /// - public Action Final - { - set - { - this.final_eh = value; - if (solver != null) - Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, _final); + /// + public Action Final + { + set + { + this.final_wrapper = _final; + this.final_eh = value; + if (solver != null) + Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, final_wrapper); } } /// /// Set equality event callback - /// - public EqEh Eq - { - set - { - this.eq_eh = value; - if (solver != null) - Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, _eq); - } + /// + public EqEh Eq + { + set + { + this.eq_wrapper = _eq; + this.eq_eh = value; + if (solver != null) + Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, eq_wrapper); + } } /// /// Set disequality event callback - /// - public EqEh Diseq - { - set - { - this.diseq_eh = value; - if (solver != null) - Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, _diseq); - } + /// + public EqEh Diseq + { + set + { + this.diseq_wrapper = _diseq; + this.diseq_eh = value; + if (solver != null) + Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, diseq_wrapper); + } } /// /// Set created callback - /// - public CreatedEh Created - { - set - { - this.created_eh = value; - if (solver != null) - Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, _created); - } + /// + public CreatedEh Created + { + set + { + this.created_wrapper = _created; + this.created_eh = value; + if (solver != null) + Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, created_wrapper); + } } /// /// Set decision callback - /// - public DecideEh Decide - { - set - { - this.decide_eh = value; - if (solver != null) - Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, _decide); - } + /// + public DecideEh Decide + { + set + { + this.decide_wrapper = _decide; + this.decide_eh = value; + if (solver != null) + Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, decide_wrapper); + } + } + + + /// + /// Set the next decision + /// + public void NextSplit(Expr e, uint idx, int phase) + { + Native.Z3_solver_next_split(ctx.nCtx, this.callback, e.NativeObject, idx, phase); } /// /// Track assignments to a term - /// - public void Register(Expr term) { - if (this.callback != IntPtr.Zero) { - Native.Z3_solver_propagate_register_cb(ctx.nCtx, callback, term.NativeObject); + /// + public void Register(Expr term) + { + if (this.callback != IntPtr.Zero) + { + Native.Z3_solver_propagate_register_cb(ctx.nCtx, callback, term.NativeObject); } - else { - Native.Z3_solver_propagate_register(ctx.nCtx, solver.NativeObject, term.NativeObject); + else + { + Native.Z3_solver_propagate_register(ctx.nCtx, solver.NativeObject, term.NativeObject); } - } + } } } diff --git a/src/api/dotnet/Z3Object.cs b/src/api/dotnet/Z3Object.cs index d385d9d62..432885b66 100644 --- a/src/api/dotnet/Z3Object.cs +++ b/src/api/dotnet/Z3Object.cs @@ -113,7 +113,10 @@ namespace Microsoft.Z3 return s.NativeObject; } - public Context Context + /// + /// Access Context object + /// + public Context Context { get { 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 2cd718627..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" @@ -76,3 +76,4 @@ DLL_VIS JNIEXPORT void JNICALL Java_com_microsoft_z3_Native_setInternalErrorHand { Z3_set_error_handler((Z3_context)a0, Z3JavaErrorHandler); } + 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/examples/high-level/miracle-sudoku.ts b/src/api/js/examples/high-level/miracle-sudoku.ts new file mode 100644 index 000000000..093d599f3 --- /dev/null +++ b/src/api/js/examples/high-level/miracle-sudoku.ts @@ -0,0 +1,206 @@ +import { init } from '../../build/node'; + +import type { Solver, Arith } from '../../build/node'; + +// solve the "miracle sudoku" +// https://www.youtube.com/watch?v=yKf9aUIxdb4 +// most of the interesting stuff is in `solve` +// the process is: +// - parse the board +// - create a Solver +// - create a Z3.Int variable for each square +// - for known cells, add a constraint which says the variable for that cell equals that value +// - add the usual uniqueness constraints +// - add the special "miracle sudoku" constraints +// - call `await solver.check()` +// - if the result is "sat", the board is solvable +// - call `solver.model()` to get a model, i.e. a concrete assignment of variables which satisfies the model +// - for each variable, call `model.evaluate(v)` to recover its value + +function parseSudoku(str: string) { + // derive a list of { row, col, val } records, one for each specified position + // from a string like + // ....1..3. + // ..9..5..8 + // 8.4..6.25 + // ......6.. + // ..8..4... + // 12..87... + // 3..9..2.. + // .65..8... + // 9........ + + let cells = []; + + let lines = str.trim().split('\n'); + if (lines.length !== 9) { + throw new Error(`expected 9 lines, got ${lines.length}`); + } + for (let row = 0; row < 9; ++row) { + let line = lines[row].trim(); + if (line.length !== 9) { + throw new Error(`expected line of length 9, got length ${line.length}`); + } + for (let col = 0; col < 9; ++col) { + let char = line[col]; + if (char === '.') { + continue; + } + if (char < '1' || char > '9') { + throw new Error(`expected digit or '.', got ${char}`); + } + cells.push({ row, col, value: char.codePointAt(0)! - 48 /* '0' */ }); + } + } + return cells; +} + +(async () => { + let { Context, em } = await init(); + + // if you use 'main' as your context name, you won't need to name it in types like Solver + // if you're creating multiple contexts, give them different names + // then the type system will prevent you from mixing them + let Z3 = Context('main'); + + function addSudokuConstraints(solver: Solver, cells: Arith[][]) { + // the usual constraints: + + // every square is between 1 and 9 + for (let row of cells) { + for (let cell of row) { + solver.add(cell.ge(1)); + solver.add(cell.le(9)); + } + } + + // values in each row are unique + for (let row of cells) { + solver.add(Z3.Distinct(...row)); + } + + // values in each column are unique + for (let col = 0; col < 9; ++col) { + solver.add(Z3.Distinct(...cells.map(row => row[col]))); + } + + // values in each 3x3 subdivision are unique + for (let suprow = 0; suprow < 3; ++suprow) { + for (let supcol = 0; supcol < 3; ++supcol) { + let square = []; + for (let row = 0; row < 3; ++row) { + for (let col = 0; col < 3; ++col) { + square.push(cells[suprow * 3 + row][supcol * 3 + col]); + } + } + solver.add(Z3.Distinct(...square)); + } + } + } + + function applyOffsets(x: number, y: number, offsets: [number, number][]) { + let out = []; + for (let offset of offsets) { + let rx = x + offset[0]; + let ry = y + offset[1]; + if (rx >= 0 && rx < 9 && ry >= 0 && ry < 8) { + out.push({ x: rx, y: ry }); + } + } + return out; + } + + function addMiracleConstraints(s: Solver, cells: Arith[][]) { + // the special "miracle sudoku" constraints + + // any two cells separated by a knight's move or a kings move cannot contain the same digit + let knightOffets: [number, number][] = [ + [1, -2], + [2, -1], + [2, 1], + [1, 2], + [-1, 2], + [-2, 1], + [-2, -1], + [-1, -2], + ]; + let kingOffsets: [number, number][] = [ + [1, 1], + [1, -1], + [-1, 1], + [-1, -1], + ]; // skipping immediately adjacent because those are covered by normal sudoku rules + let allOffets = [...knightOffets, ...kingOffsets]; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + for (let { x, y } of applyOffsets(row, col, allOffets)) { + s.add(cells[row][col].neq(cells[x][y])); + } + } + } + + // any two orthogonally adjacent cells cannot contain consecutive digits + let orthoOffsets: [number, number][] = [ + [0, 1], + [0, -1], + [1, 0], + [-1, 0], + ]; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + for (let { x, y } of applyOffsets(row, col, orthoOffsets)) { + s.add(cells[row][col].sub(cells[x][y]).neq(1)); + } + } + } + } + + async function solve(str: string) { + let solver = new Z3.Solver(); + let cells = Array.from({ length: 9 }, (_, col) => Array.from({ length: 9 }, (_, row) => Z3.Int.const(`c_${row}_${col}`))); + for (let { row, col, value } of parseSudoku(str)) { + solver.add(cells[row][col].eq(value)); + } + addSudokuConstraints(solver, cells); + addMiracleConstraints(solver, cells); // remove this line to solve normal sudokus + + let start = Date.now(); + console.log('starting... this may take a minute or two'); + let check = await solver.check(); + console.log(`problem was determined to be ${check} in ${Date.now() - start} ms`); + if (check === 'sat') { + let model = solver.model(); + let str = ''; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + str += model.eval(cells[row][col]).toString() + (col === 8 ? '' : ' '); + if (col === 2 || col === 5) { + str += ' '; + } + } + str += '\n'; + if (row === 2 || row === 5) { + str += '\n'; + } + } + console.log(str); + } + } + + await solve(` +......... +......... +......... +......... +..1...... +......2.. +......... +......... +......... +`); + + em.PThread.terminateAllThreads(); +})().catch(e => { + console.error('error', e); + process.exit(1); +}); 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/js/src/high-level/high-level.test.ts b/src/api/js/src/high-level/high-level.test.ts index 9555eea31..9ec7d6eca 100644 --- a/src/api/js/src/high-level/high-level.test.ts +++ b/src/api/js/src/high-level/high-level.test.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import asyncToArray from 'iter-tools/methods/async-to-array'; import { init, killThreads } from '../jest'; -import { Arith, Bool, Model, sat, unsat, Z3AssertionError, Z3HighLevel } from './types'; +import { Arith, Bool, Model, Z3AssertionError, Z3HighLevel } from './types'; /** * Generate all possible solutions from given assumptions. @@ -31,7 +31,7 @@ async function* allSolutions(...assertions: Bool[]): const solver = new assertions[0].ctx.Solver(); solver.add(...assertions); - while ((await solver.check()) === sat) { + while ((await solver.check()) === 'sat') { const model = solver.model(); const decls = model.decls(); if (decls.length === 0) { @@ -59,13 +59,13 @@ async function prove(conjecture: Bool): Promise { const solver = new conjecture.ctx.Solver(); const { Not } = solver.ctx; solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); } async function solve(conjecture: Bool): Promise { const solver = new conjecture.ctx.Solver(); solver.add(conjecture); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); return solver.model(); } @@ -96,7 +96,7 @@ describe('high-level', () => { }); it('proves x = y implies g(x) = g(y)', async () => { - const { Solver, Int, Function, Implies, Not } = new api.Context('main'); + const { Solver, Int, Function, Implies, Not } = api.Context('main'); const solver = new Solver(); const sort = Int.sort(); @@ -106,11 +106,11 @@ describe('high-level', () => { const conjecture = Implies(x.eq(y), g.call(x).eq(g.call(y))); solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); }); it('disproves x = y implies g(g(x)) = g(y)', async () => { - const { Solver, Int, Function, Implies, Not } = new api.Context('main'); + const { Solver, Int, Function, Implies, Not } = api.Context('main'); const solver = new Solver(); const sort = Int.sort(); @@ -119,14 +119,14 @@ describe('high-level', () => { const g = Function.declare('g', sort, sort); const conjecture = Implies(x.eq(y), g.call(g.call(x)).eq(g.call(y))); solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); it('checks that Context matches', () => { - const c1 = new api.Context('context'); - const c2 = new api.Context('context'); - const c3 = new api.Context('foo'); - const c4 = new api.Context('bar'); + const c1 = api.Context('context'); + const c2 = api.Context('context'); + const c3 = api.Context('foo'); + const c4 = api.Context('bar'); // Contexts with the same name don't do type checking during compile time. // We need to check for different context dynamically @@ -144,7 +144,7 @@ describe('high-level', () => { describe('booleans', () => { it("proves De Morgan's Law", async () => { - const { Bool, Not, And, Eq, Or } = new api.Context('main'); + const { Bool, Not, And, Eq, Or } = api.Context('main'); const [x, y] = [Bool.const('x'), Bool.const('y')]; const conjecture = Eq(Not(And(x, y)), Or(Not(x), Not(y))); @@ -155,7 +155,7 @@ describe('high-level', () => { describe('ints', () => { it('finds a model', async () => { - const { Solver, Int, isIntVal } = new api.Context('main'); + const { Solver, Int, isIntVal } = api.Context('main'); const solver = new Solver(); const x = Int.const('x'); const y = Int.const('y'); @@ -163,10 +163,10 @@ describe('high-level', () => { solver.add(x.ge(1)); // x >= 1 solver.add(y.lt(x.add(3))); // y < x + 3 - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); - expect(model.length).toStrictEqual(2); + expect(model.length()).toStrictEqual(2); for (const decl of model) { expect(decl.arity()).toStrictEqual(0); @@ -175,8 +175,8 @@ describe('high-level', () => { const yValueExpr = model.get(y); assert(isIntVal(xValueExpr)); assert(isIntVal(yValueExpr)); - const xValue = xValueExpr.value; - const yValue = yValueExpr.value; + const xValue = xValueExpr.value(); + const yValue = yValueExpr.value(); assert(typeof xValue === 'bigint'); assert(typeof yValue === 'bigint'); expect(xValue).toBeGreaterThanOrEqual(1n); @@ -225,7 +225,7 @@ describe('high-level', () => { 541972386 `); - const { Solver, Int, Distinct, isIntVal } = new api.Context('main'); + const { Solver, Int, Distinct, isIntVal } = api.Context('main'); const cells: Arith[][] = []; // 9x9 matrix of integer variables @@ -284,7 +284,7 @@ describe('high-level', () => { } } - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); const result = []; @@ -293,7 +293,7 @@ describe('high-level', () => { for (let j = 0; j < 9; j++) { const cell = model.eval(cells[i][j]); assert(isIntVal(cell)); - const value = cell.value; + const value = cell.value(); assert(typeof value === 'bigint'); expect(value).toBeGreaterThanOrEqual(0n); expect(value).toBeLessThanOrEqual(9n); @@ -308,7 +308,7 @@ describe('high-level', () => { describe('reals', () => { it('can work with numerals', async () => { - const { Real, And } = new api.Context('main'); + const { Real, And } = api.Context('main'); const n1 = Real.val('1/2'); const n2 = Real.val('0.5'); const n3 = Real.val(0.5); @@ -322,7 +322,7 @@ describe('high-level', () => { it('can do non-linear arithmetic', async () => { api.setParam('pp.decimal', true); api.setParam('pp.decimal_precision', 20); - const { Real, Solver, isReal, isRealVal } = new api.Context('main'); + const { Real, Solver, isReal, isRealVal } = api.Context('main'); const x = Real.const('x'); const y = Real.const('y'); const z = Real.const('z'); @@ -331,7 +331,7 @@ describe('high-level', () => { solver.add(x.mul(x).add(y.mul(y)).eq(1)); // x^2 + y^2 == 1 solver.add(x.mul(x).mul(x).add(z.mul(z).mul(z)).lt('1/2')); // x^3 + z^3 < 1/2 - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); expect(isRealVal(model.get(x))).toStrictEqual(true); @@ -344,7 +344,7 @@ describe('high-level', () => { describe('bitvectors', () => { it('can do simple proofs', async () => { - const { BitVec, Concat, Implies, isBitVecVal } = new api.Context('main'); + const { BitVec, Concat, Implies, isBitVecVal } = api.Context('main'); const x = BitVec.const('x', 32); @@ -354,7 +354,7 @@ describe('high-level', () => { assert(isBitVecVal(sSol) && isBitVecVal(uSol)); let v = sSol.asSignedValue(); expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true); - v = uSol.value; + v = uSol.value(); expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true); const y = BitVec.const('y', 32); @@ -363,7 +363,7 @@ describe('high-level', () => { }); it('finds x and y such that: x ^ y - 103 == x * y', async () => { - const { BitVec, isBitVecVal } = new api.Context('main'); + const { BitVec, isBitVecVal } = api.Context('main'); const x = BitVec.const('x', 32); const y = BitVec.const('y', 32); @@ -382,28 +382,28 @@ describe('high-level', () => { describe('Solver', () => { it('can use push and pop', async () => { - const { Solver, Int } = new api.Context('main'); + const { Solver, Int } = api.Context('main'); const solver = new Solver(); const x = Int.const('x'); solver.add(x.gt(0)); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); solver.push(); solver.add(x.lt(0)); expect(solver.numScopes()).toStrictEqual(1); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); solver.pop(); expect(solver.numScopes()).toStrictEqual(0); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); it('can find multiple solutions', async () => { - const { Int, isIntVal } = new api.Context('main'); + const { Int, isIntVal } = api.Context('main'); const x = Int.const('x'); @@ -413,7 +413,7 @@ describe('high-level', () => { .map(solution => { const expr = solution.eval(x); assert(isIntVal(expr)); - return expr.value; + return expr.value(); }) .sort((a, b) => { assert(a !== null && b !== null && typeof a === 'bigint' && typeof b === 'bigint'); @@ -431,7 +431,7 @@ describe('high-level', () => { describe('AstVector', () => { it('can use basic methods', async () => { - const { Solver, AstVector, Int } = new api.Context('main'); + const { Solver, AstVector, Int } = api.Context('main'); const solver = new Solver(); const vector = new AstVector(); @@ -439,12 +439,12 @@ describe('high-level', () => { vector.push(Int.const(`int__${i}`)); } - const length = vector.length; + const length = vector.length(); for (let i = 0; i < length; i++) { solver.add(vector.get(i).gt(1)); } - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); }); }); diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index 731e47b07..489d4acdb 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -18,6 +18,7 @@ import { Z3_ast_vector, Z3_context, Z3_decl_kind, + Z3_error_code, Z3_func_decl, Z3_func_interp, Z3_lbool, @@ -62,17 +63,14 @@ import { Model, Probe, RatNum, - sat, Solver, Sort, SortToExprMap, Tactic, - unknown, - unsat, Z3Error, Z3HighLevel, } from './types'; -import { allSatisfy, assert, assertExhaustive, autoBind } from './utils'; +import { allSatisfy, assert, assertExhaustive } from './utils'; const FALLBACK_PRECISION = 17; @@ -150,738 +148,112 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return Z3.global_param_get(name); } - function isContext(obj: unknown): obj is Context { - return obj instanceof ContextImpl; - } + function createContext(name: Name, options?: Record): Context { + const cfg = Z3.mk_config(); + if (options != null) { + Object.entries(options).forEach(([key, value]) => check(Z3.set_param_value(cfg, key, value.toString()))); + } + const contextPtr = Z3.mk_context_rc(cfg); + Z3.set_ast_print_mode(contextPtr, Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT); + Z3.del_config(cfg); - class ContextImpl implements Context { - declare readonly __typename: Context['__typename']; - - readonly ptr: Z3_context; - readonly name: string; - - constructor(name: string, params: Record = {}) { - const cfg = Z3.mk_config(); - Object.entries(params).forEach(([key, value]) => Z3.set_param_value(cfg, key, value.toString())); - const context = Z3.mk_context_rc(cfg); - - this.ptr = context; - this.name = name; - - Z3.set_ast_print_mode(this.ptr, Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT); - Z3.del_config(cfg); - - // We want to bind functions and operations to `this` inside Context - // So that the user can write things like this and have it work: - // ``` - // const { And, Or } = new Context('main'); - // ``` - // - // Typescript doesn't handle overloading of method fields, only - // methods. We can't use closures to bind this, so we use auto-bind library - // ``` - // class { - // // This works - // test(a: boolean): boolean; - // test(a: number): number; - // test(a: boolean | number): boolean | number { - // return 0; - // } - // - // // This fails to compile - // test2: (a: boolean) => boolean; - // test2: (a: number) => number; - // test2 = (a: boolean | number): boolean | number => { - // return 0; - // } - // } - // ``` - autoBind(this); - - cleanup.register(this, () => Z3.del_context(context)); + function _assertContext(...ctxs: (Context | { ctx: Context })[]) { + ctxs.forEach(other => assert('ctx' in other ? ctx === other.ctx : ctx === other, 'Context mismatch')); } - /////////////// - // Functions // - /////////////// - interrupt(): void { - Z3.interrupt(this.ptr); - } - - isModel(obj: unknown): obj is Model { - const r = obj instanceof ModelImpl; - r && this._assertContext(obj); - return r; - } - - isAst(obj: unknown): obj is Ast { - const r = obj instanceof AstImpl; - r && this._assertContext(obj); - return r; - } - - isSort(obj: unknown): obj is Sort { - const r = obj instanceof SortImpl; - r && this._assertContext(obj); - return r; - } - - isFuncDecl(obj: unknown): obj is FuncDecl { - const r = obj instanceof FuncDeclImpl; - r && this._assertContext(obj); - return r; - } - - isApp(obj: unknown): boolean { - if (!this.isExpr(obj)) { - return false; - } - const kind = Z3.get_ast_kind(this.ptr, obj.ast); - return kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST; - } - - isConst(obj: unknown): boolean { - return this.isExpr(obj) && this.isApp(obj) && obj.numArgs() === 0; - } - - isExpr(obj: unknown): obj is Expr { - const r = obj instanceof ExprImpl; - r && this._assertContext(obj); - return r; - } - - isVar(obj: unknown): boolean { - return this.isExpr(obj) && Z3.get_ast_kind(this.ptr, obj.ast) === Z3_ast_kind.Z3_VAR_AST; - } - - isAppOf(obj: unknown, kind: Z3_decl_kind): boolean { - return this.isExpr(obj) && this.isApp(obj) && obj.decl().kind() === kind; - } - - isBool(obj: unknown): obj is Bool { - const r = obj instanceof BoolImpl; - r && this._assertContext(obj); - return r; - } - - isTrue(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_TRUE); - } - - isFalse(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_FALSE); - } - - isAnd(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_AND); - } - - isOr(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_OR); - } - - isImplies(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_IMPLIES); - } - - isNot(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_NOT); - } - - isEq(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_EQ); - } - - isDistinct(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_DISTINCT); - } - - isArith(obj: unknown): obj is Arith { - const r = obj instanceof ArithImpl; - r && this._assertContext(obj); - return r; - } - - isArithSort(obj: unknown): obj is ArithSort { - const r = obj instanceof ArithSortImpl; - r && this._assertContext(obj); - return r; - } - - isInt(obj: unknown): boolean { - return this.isArith(obj) && this.isIntSort(obj.sort); - } - - isIntVal(obj: unknown): obj is IntNum { - const r = obj instanceof IntNumImpl; - r && this._assertContext(obj); - return r; - } - - isIntSort(obj: unknown): boolean { - return this.isSort(obj) && obj.kind() === Z3_sort_kind.Z3_INT_SORT; - } - - isReal(obj: unknown): boolean { - return this.isArith(obj) && this.isRealSort(obj.sort); - } - - isRealVal(obj: unknown): obj is RatNum { - const r = obj instanceof RatNumImpl; - r && this._assertContext(obj); - return r; - } - - isRealSort(obj: unknown): boolean { - return this.isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT; - } - - isBitVecSort(obj: unknown): obj is BitVecSort { - const r = obj instanceof BitVecSortImpl; - r && this._assertContext(obj); - return r; - } - - isBitVec(obj: unknown): obj is BitVec { - const r = obj instanceof BitVecImpl; - r && this._assertContext(obj); - return r; - } - - isBitVecVal(obj: unknown): obj is BitVecNum { - const r = obj instanceof BitVecNumImpl; - r && this._assertContext(obj); - return r; - } - - isProbe(obj: unknown): obj is Probe { - const r = obj instanceof ProbeImpl; - r && this._assertContext(obj); - return r; - } - - isTactic(obj: unknown): obj is Tactic { - const r = obj instanceof TacticImpl; - r && this._assertContext(obj); - return r; - } - - isAstVector(obj: unknown): obj is AstVector { - const r = obj instanceof AstVectorImpl; - r && this._assertContext(obj); - return r; - } - - eqIdentity(a: Ast, b: Ast): boolean { - return a.eqIdentity(b); - } - - getVarIndex(obj: Expr): number { - assert(this.isVar(obj), 'Z3 bound variable expected'); - return Z3.get_index_value(this.ptr, obj.ast); - } - - from(primitive: boolean): Bool; - from(primitive: number | CoercibleRational): RatNum; - from(primitive: bigint): IntNum; - from(expr: T): T; - from(expr: CoercibleToExpr): AnyExpr; - from(value: CoercibleToExpr): AnyExpr { - if (typeof value === 'boolean') { - return this.Bool.val(value); - } else if (typeof value === 'number' || isCoercibleRational(value)) { - return this.Real.val(value); - } else if (typeof value === 'bigint') { - return this.Int.val(value); - } else if (this.isExpr(value)) { - return value; - } - assert(false); - } - - async solve(...assertions: Bool[]): Promise { - const solver = new this.Solver(); - solver.add(...assertions); - const result = await solver.check(); - if (result === sat) { - return solver.model(); - } - return result; - } - - ///////////// - // Classes // - ///////////// - readonly Solver = SolverImpl.bind(SolverImpl, this); - readonly Model = ModelImpl.bind(ModelImpl, this); - readonly Tactic = TacticImpl.bind(TacticImpl, this); - readonly AstVector = AstVectorImpl.bind(AstVectorImpl, this) as AstVectorCtor; - readonly AstMap = AstMapImpl.bind(AstMapImpl, this) as AstMapCtor; - - ///////////// - // Objects // - ///////////// - readonly Sort = { - declare: (name: string) => new SortImpl(this, Z3.mk_uninterpreted_sort(this.ptr, this._toSymbol(name))), - }; - readonly Function = { - declare: (name: string, ...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_func_decl(this.ptr, this._toSymbol(name), dom, rng.ptr)); - }, - fresh: (...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_fresh_func_decl(this.ptr, 'f', dom, rng.ptr)); - }, - }; - readonly RecFunc = { - declare: (name: string, ...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_rec_func_decl(this.ptr, this._toSymbol(name), dom, rng.ptr)); - }, - - addDefinition: (f: FuncDecl, args: Expr[], body: Expr) => { - this._assertContext(f, ...args, body); - Z3.add_rec_def( - this.ptr, - f.ptr, - args.map(arg => arg.ast), - body.ast, - ); - }, - }; - readonly Bool = { - sort: () => new BoolSortImpl(this, Z3.mk_bool_sort(this.ptr)), - - const: (name: string) => new BoolImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Bool.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Bool.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Bool.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'b') => new BoolImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Bool.sort().ptr)), - - val: (value: boolean) => { - if (value) { - return new BoolImpl(this, Z3.mk_true(this.ptr)); - } - return new BoolImpl(this, Z3.mk_false(this.ptr)); - }, - }; - readonly Int = { - sort: () => new ArithSortImpl(this, Z3.mk_int_sort(this.ptr)), - - const: (name: string) => new ArithImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Int.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Int.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Int.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'x') => new ArithImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Int.sort().ptr)), - - val: (value: number | bigint | string) => { - assert(typeof value === 'bigint' || typeof value === 'string' || Number.isSafeInteger(value)); - return new IntNumImpl(this, Z3.mk_numeral(this.ptr, value.toString(), this.Int.sort().ptr)); - }, - }; - readonly Real = { - sort: () => new ArithSortImpl(this, Z3.mk_real_sort(this.ptr)), - - const: (name: string) => new ArithImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Real.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Real.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Real.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'b') => new ArithImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Real.sort().ptr)), - - val: (value: number | bigint | string | CoercibleRational) => { - if (isCoercibleRational(value)) { - value = `${value.numerator}/${value.denominator}`; - } - return new RatNumImpl(this, Z3.mk_numeral(this.ptr, value.toString(), this.Real.sort().ptr)); - }, - }; - readonly BitVec = { - sort: (bits: number): BitVecSort => { - assert(Number.isSafeInteger(bits), 'number of bits must be an integer'); - return new BitVecSortImpl(this, Z3.mk_bv_sort(this.ptr, bits)); - }, - - const: (name: string, bits: number | BitVecSort): BitVec => - new BitVecImpl( - this, - Z3.mk_const(this.ptr, this._toSymbol(name), this.isBitVecSort(bits) ? bits.ptr : this.BitVec.sort(bits).ptr), - ), - - consts: (names: string | string[], bits: number | BitVecSort): BitVec[] => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.BitVec.const(name, bits)); - }, - - val: (value: bigint | number | boolean, bits: number | BitVecSort): BitVecNum => { - if (value === true) { - return this.BitVec.val(1, bits); - } else if (value === false) { - return this.BitVec.val(0, bits); - } - return new BitVecNumImpl( - this, - Z3.mk_numeral(this.ptr, value.toString(), this.isBitVecSort(bits) ? bits.ptr : this.BitVec.sort(bits).ptr), - ); - }, - }; - - //////////////// - // Operations // - //////////////// - If(condition: Probe, onTrue: Tactic, onFalse: Tactic): Tactic; - If( - condition: Bool | boolean, - onTrue: OnTrueRef, - onFalse: OnFalseRef, - ): CoercibleToExprMap; - If( - condition: Bool | Probe | boolean, - onTrue: CoercibleToExpr | Tactic, - onFalse: CoercibleToExpr | Tactic, - ): Expr | Tactic { - if (this.isProbe(condition) && this.isTactic(onTrue) && this.isTactic(onFalse)) { - return this.Cond(condition, onTrue, onFalse); - } - assert( - !this.isProbe(condition) && !this.isTactic(onTrue) && !this.isTactic(onFalse), - 'Mixed expressions and goals', - ); - if (typeof condition === 'boolean') { - condition = this.Bool.val(condition); - } - onTrue = this.from(onTrue); - onFalse = this.from(onFalse); - return this._toExpr(Z3.mk_ite(this.ptr, condition.ptr, onTrue.ast, onFalse.ast)); - } - - Distinct(...exprs: CoercibleToExpr[]): Bool { - assert(exprs.length > 0, "Can't make Distinct ouf of nothing"); - - return new BoolImpl( - this, - Z3.mk_distinct( - this.ptr, - exprs.map(expr => { - expr = this.from(expr); - this._assertContext(expr); - return expr.ast; - }), - ), - ); - } - - Const(name: string, sort: S): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_const(this.ptr, this._toSymbol(name), sort.ptr)) as SortToExprMap; - } - - Consts(names: string | string[], sort: S): SortToExprMap[] { - this._assertContext(sort); - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Const(name, sort)); - } - - FreshConst(sort: S, prefix: string = 'c'): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_fresh_const(sort.ctx.ptr, prefix, sort.ptr)) as SortToExprMap; - } - - Var(idx: number, sort: S): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_bound(sort.ctx.ptr, idx, sort.ptr)) as SortToExprMap; - } - - Implies(a: Bool | boolean, b: Bool | boolean): Bool { - a = this.from(a) as Bool; - b = this.from(b) as Bool; - this._assertContext(a, b); - return new BoolImpl(this, Z3.mk_implies(this.ptr, a.ptr, b.ptr)); - } - - Eq(a: CoercibleToExpr, b: CoercibleToExpr): Bool { - a = this.from(a); - b = this.from(b); - this._assertContext(a, b); - return a.eq(b); - } - - Xor(a: Bool | boolean, b: Bool | boolean): Bool { - a = this.from(a) as Bool; - b = this.from(b) as Bool; - this._assertContext(a, b); - return new BoolImpl(this, Z3.mk_xor(this.ptr, a.ptr, b.ptr)); - } - - Not(a: Probe): Probe; - Not(a: Bool | boolean): Bool; - Not(a: Bool | boolean | Probe): Bool | Probe { - if (typeof a === 'boolean') { - a = this.from(a); - } - this._assertContext(a); - if (this.isProbe(a)) { - return new ProbeImpl(this, Z3.probe_not(this.ptr, a.ptr)); - } - return new BoolImpl(this, Z3.mk_not(this.ptr, a.ptr)); - } - - And(): Bool; - And(vector: AstVector): Bool; - And(...args: (Bool | boolean)[]): Bool; - And(...args: Probe[]): Probe; - And(...args: (AstVector | Probe | Bool | boolean)[]): Bool | Probe { - if (args.length == 1 && args[0] instanceof this.AstVector) { - args = [...args[0].values()]; - assert(allSatisfy(args, this.isBool.bind(this)) ?? true, 'AstVector containing not bools'); - } - - const allProbes = allSatisfy(args, this.isProbe.bind(this)) ?? false; - if (allProbes) { - return this._probeNary(Z3.probe_and, args as [Probe, ...Probe[]]); - } else { - args = args.map(this.from.bind(this)) as Bool[]; - this._assertContext(...(args as Bool[])); - return new BoolImpl( - this, - Z3.mk_and( - this.ptr, - args.map(arg => (arg as Bool).ptr), - ), - ); + // call this after every nontrivial call to the underlying API + function throwIfError() { + if (Z3.get_error_code(contextPtr) !== Z3_error_code.Z3_OK) { + throw new Error(Z3.get_error_msg(ctx.ptr, Z3.get_error_code(ctx.ptr))); } } - Or(): Bool; - Or(vector: AstVector): Bool; - Or(...args: (Bool | boolean)[]): Bool; - Or(...args: Probe[]): Probe; - Or(...args: (AstVector | Probe | Bool | boolean)[]): Bool | Probe { - if (args.length == 1 && args[0] instanceof this.AstVector) { - args = [...args[0].values()]; - assert(allSatisfy(args, this.isBool.bind(this)) ?? true, 'AstVector containing not bools'); - } - - const allProbes = allSatisfy(args, this.isProbe.bind(this)) ?? false; - if (allProbes) { - return this._probeNary(Z3.probe_or, args as [Probe, ...Probe[]]); - } else { - args = args.map(this.from.bind(this)) as Bool[]; - this._assertContext(...(args as Bool[])); - return new BoolImpl( - this, - Z3.mk_or( - this.ptr, - args.map(arg => (arg as Bool).ptr), - ), - ); - } - } - - ToReal(expr: Arith | bigint): Arith { - expr = this.from(expr) as Arith; - this._assertContext(expr); - assert(this.isInt(expr), 'Int expression expected'); - return new ArithImpl(this, Z3.mk_int2real(this.ptr, expr.ast)); - } - - ToInt(expr: Arith | number | CoercibleRational | string): Arith { - if (!this.isExpr(expr)) { - expr = this.Real.val(expr); - } - this._assertContext(expr); - assert(this.isReal(expr), 'Real expression expected'); - return new ArithImpl(this, Z3.mk_real2int(this.ptr, expr.ast)); - } - - IsInt(expr: Arith | number | CoercibleRational | string): Bool { - if (!this.isExpr(expr)) { - expr = this.Real.val(expr); - } - this._assertContext(expr); - assert(this.isReal(expr), 'Real expression expected'); - return new BoolImpl(this, Z3.mk_is_int(this.ptr, expr.ast)); - } - - Sqrt(a: Arith | number | bigint | string | CoercibleRational): Arith { - if (!this.isExpr(a)) { - a = this.Real.val(a); - } - return a.pow('1/2'); - } - - Cbrt(a: Arith | number | bigint | string | CoercibleRational): Arith { - if (!this.isExpr(a)) { - a = this.Real.val(a); - } - return a.pow('1/3'); - } - - BV2Int(a: BitVec, isSigned: boolean): Arith { - this._assertContext(a); - return new ArithImpl(this, Z3.mk_bv2int(this.ptr, a.ast, isSigned)); - } - - Int2BV(a: Arith | bigint | number, bits: number): BitVec { - if (this.isArith(a)) { - assert(this.isInt(a), 'parameter must be an integer'); - } else { - assert(typeof a !== 'number' || Number.isSafeInteger(a), 'parameter must not have decimal places'); - a = this.Int.val(a); - } - return new BitVecImpl(this, Z3.mk_int2bv(this.ptr, bits, a.ast)); - } - - Concat(...bitvecs: BitVec[]): BitVec { - this._assertContext(...bitvecs); - return bitvecs.reduce((prev, curr) => new BitVecImpl(this, Z3.mk_concat(this.ptr, prev.ast, curr.ast))); - } - - Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic { - this._assertContext(probe, onTrue, onFalse); - return new this.Tactic(Z3.tactic_cond(this.ptr, probe.ptr, onTrue.ptr, onFalse.ptr)); + function check(val: T) { + throwIfError(); + return val; } ///////////// // Private // ///////////// - _assertContext(...ctxs: (Context | { ctx: Context })[]) { - ctxs.forEach(other => assert('ctx' in other ? this === other.ctx : this === other, 'Context mismatch')); - } - - _toSymbol(s: string | number) { + function _toSymbol(s: string | number) { if (typeof s === 'number') { - return Z3.mk_int_symbol(this.ptr, s); + return check(Z3.mk_int_symbol(contextPtr, s)); } else { - return Z3.mk_string_symbol(this.ptr, s); + return check(Z3.mk_string_symbol(contextPtr, s)); } } - _fromSymbol(sym: Z3_symbol) { - const kind = Z3.get_symbol_kind(this.ptr, sym); + function _fromSymbol(sym: Z3_symbol) { + const kind = check(Z3.get_symbol_kind(contextPtr, sym)); switch (kind) { case Z3_symbol_kind.Z3_INT_SYMBOL: - return Z3.get_symbol_int(this.ptr, sym); + return Z3.get_symbol_int(contextPtr, sym); case Z3_symbol_kind.Z3_STRING_SYMBOL: - return Z3.get_symbol_string(this.ptr, sym); + return Z3.get_symbol_string(contextPtr, sym); default: assertExhaustive(kind); } } - _toAst(ast: Z3_ast): AnyAst { - switch (Z3.get_ast_kind(this.ptr, ast)) { + function _toAst(ast: Z3_ast): AnyAst { + switch (check(Z3.get_ast_kind(contextPtr, ast))) { case Z3_ast_kind.Z3_SORT_AST: - return this._toSort(ast as Z3_sort); + return _toSort(ast as Z3_sort); case Z3_ast_kind.Z3_FUNC_DECL_AST: - return new FuncDeclImpl(this, ast as Z3_func_decl); + return new FuncDeclImpl(ast as Z3_func_decl); default: - return this._toExpr(ast); + return _toExpr(ast); } } - _toSort(ast: Z3_sort): AnySort { - switch (Z3.get_sort_kind(this.ptr, ast)) { + function _toSort(ast: Z3_sort): AnySort { + switch (check(Z3.get_sort_kind(contextPtr, ast))) { case Z3_sort_kind.Z3_BOOL_SORT: - return new BoolSortImpl(this, ast); + return new BoolSortImpl(ast); case Z3_sort_kind.Z3_INT_SORT: case Z3_sort_kind.Z3_REAL_SORT: - return new ArithSortImpl(this, ast); + return new ArithSortImpl(ast); case Z3_sort_kind.Z3_BV_SORT: - return new BitVecSortImpl(this, ast); + return new BitVecSortImpl(ast); default: - return new SortImpl(this, ast); + return new SortImpl(ast); } } - _toExpr(ast: Z3_ast): Bool | IntNum | RatNum | Arith | Expr { - const kind = Z3.get_ast_kind(this.ptr, ast); + function _toExpr(ast: Z3_ast): Bool | IntNum | RatNum | Arith | Expr { + const kind = check(Z3.get_ast_kind(contextPtr, ast)); if (kind === Z3_ast_kind.Z3_QUANTIFIER_AST) { assert(false); } - const sortKind = Z3.get_sort_kind(this.ptr, Z3.get_sort(this.ptr, ast)); + const sortKind = check(Z3.get_sort_kind(contextPtr, Z3.get_sort(contextPtr, ast))); switch (sortKind) { case Z3_sort_kind.Z3_BOOL_SORT: - return new BoolImpl(this, ast); + return new BoolImpl(ast); case Z3_sort_kind.Z3_INT_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new IntNumImpl(this, ast); + return new IntNumImpl(ast); } - return new ArithImpl(this, ast); + return new ArithImpl(ast); case Z3_sort_kind.Z3_REAL_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new RatNumImpl(this, ast); + return new RatNumImpl(ast); } - return new ArithImpl(this, ast); + return new ArithImpl(ast); case Z3_sort_kind.Z3_BV_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new BitVecNumImpl(this, ast); + return new BitVecNumImpl(ast); } - return new BitVecImpl(this, ast); + return new BitVecImpl(ast); default: - return new ExprImpl(this, ast); + return new ExprImpl(ast); } } - _flattenArgs(args: (T | AstVector)[]): T[] { + function _flattenArgs = AnyAst>(args: (T | AstVector)[]): T[] { const result: T[] = []; for (const arg of args) { - if (this.isAstVector(arg)) { + if (isAstVector(arg)) { result.push(...arg.values()); } else { result.push(arg); @@ -890,984 +262,1694 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return result; } - _toProbe(p: Probe | Z3_probe): Probe { - if (this.isProbe(p)) { + function _toProbe(p: Probe | Z3_probe): Probe { + if (isProbe(p)) { return p; } - return new ProbeImpl(this, p); + return new ProbeImpl(p); } - _probeNary( + function _probeNary( f: (ctx: Z3_context, left: Z3_probe, right: Z3_probe) => Z3_probe, - args: [Probe | Z3_probe, ...(Probe | Z3_probe)[]], + args: [Probe | Z3_probe, ...(Probe | Z3_probe)[]], ) { assert(args.length > 0, 'At least one argument expected'); - let r = this._toProbe(args[0]); + let r = _toProbe(args[0]); for (let i = 1; i < args.length; i++) { - r = new ProbeImpl(this, f(this.ptr, r.ptr, this._toProbe(args[i]).ptr)); + r = new ProbeImpl(check(f(contextPtr, r.ptr, _toProbe(args[i]).ptr))); } return r; } - } - class AstImpl implements Ast { - declare readonly __typename: Ast['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Ptr) { - const myAst = this.ast; - - Z3.inc_ref(ctx.ptr, myAst); - cleanup.register(this, () => Z3.dec_ref(ctx.ptr, myAst)); + /////////////// + // Functions // + /////////////// + function interrupt(): void { + check(Z3.interrupt(contextPtr)); } - get ast(): Z3_ast { - return this.ptr as any as Z3_ast; + function isModel(obj: unknown): obj is Model { + const r = obj instanceof ModelImpl; + r && _assertContext(obj); + return r; } - get id() { - return Z3.get_ast_id(this.ctx.ptr, this.ast); + function isAst(obj: unknown): obj is Ast { + const r = obj instanceof AstImpl; + r && _assertContext(obj); + return r; } - eqIdentity(other: Ast) { - this.ctx._assertContext(other); - return Z3.is_eq_ast(this.ctx.ptr, this.ast, other.ast); + function isSort(obj: unknown): obj is Sort { + const r = obj instanceof SortImpl; + r && _assertContext(obj); + return r; } - neqIdentity(other: Ast) { - this.ctx._assertContext(other); - return !this.eqIdentity(other); + function isFuncDecl(obj: unknown): obj is FuncDecl { + const r = obj instanceof FuncDeclImpl; + r && _assertContext(obj); + return r; } - sexpr() { - return Z3.ast_to_string(this.ctx.ptr, this.ast); - } - - hash() { - return Z3.get_ast_hash(this.ctx.ptr, this.ast); - } - } - - class SolverImpl implements Solver { - declare readonly __typename: Solver['__typename']; - - readonly ptr: Z3_solver; - - constructor(readonly ctx: ContextImpl, ptr: Z3_solver | string = Z3.mk_solver(ctx.ptr)) { - let myPtr: Z3_solver; - if (typeof ptr === 'string') { - myPtr = Z3.mk_solver_for_logic(ctx.ptr, ctx._toSymbol(ptr)); - } else { - myPtr = ptr; + function isApp(obj: unknown): boolean { + if (!isExpr(obj)) { + return false; } - this.ptr = myPtr; - Z3.solver_inc_ref(ctx.ptr, myPtr); - cleanup.register(this, () => Z3.solver_dec_ref(ctx.ptr, myPtr)); + const kind = check(Z3.get_ast_kind(contextPtr, obj.ast)); + return kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST; } - push() { - Z3.solver_push(this.ctx.ptr, this.ptr); + function isConst(obj: unknown): boolean { + return isExpr(obj) && isApp(obj) && obj.numArgs() === 0; } - pop(num: number = 1) { - Z3.solver_pop(this.ctx.ptr, this.ptr, num); + + function isExpr(obj: unknown): obj is Expr { + const r = obj instanceof ExprImpl; + r && _assertContext(obj); + return r; } - numScopes() { - return Z3.solver_get_num_scopes(this.ctx.ptr, this.ptr); + + function isVar(obj: unknown): boolean { + return isExpr(obj) && check(Z3.get_ast_kind(contextPtr, obj.ast)) === Z3_ast_kind.Z3_VAR_AST; } - reset() { - Z3.solver_reset(this.ctx.ptr, this.ptr); + + function isAppOf(obj: unknown, kind: Z3_decl_kind): boolean { + return isExpr(obj) && isApp(obj) && obj.decl().kind() === kind; } - add(...exprs: (Bool | AstVector)[]) { - this.ctx._flattenArgs(exprs).forEach(expr => { - this.ctx._assertContext(expr); - Z3.solver_assert(this.ctx.ptr, this.ptr, expr.ast); - }); + + function isBool(obj: unknown): obj is Bool { + const r = obj instanceof BoolImpl; + r && _assertContext(obj); + return r; } - addAndTrack(expr: Bool, constant: Bool | string) { - if (typeof constant === 'string') { - constant = this.ctx.Bool.const(constant); + + function isTrue(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_TRUE); + } + + function isFalse(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_FALSE); + } + + function isAnd(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_AND); + } + + function isOr(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_OR); + } + + function isImplies(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_IMPLIES); + } + + function isNot(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_NOT); + } + + function isEq(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_EQ); + } + + function isDistinct(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_DISTINCT); + } + + function isArith(obj: unknown): obj is Arith { + const r = obj instanceof ArithImpl; + r && _assertContext(obj); + return r; + } + + function isArithSort(obj: unknown): obj is ArithSort { + const r = obj instanceof ArithSortImpl; + r && _assertContext(obj); + return r; + } + + function isInt(obj: unknown): boolean { + return isArith(obj) && isIntSort(obj.sort); + } + + function isIntVal(obj: unknown): obj is IntNum { + const r = obj instanceof IntNumImpl; + r && _assertContext(obj); + return r; + } + + function isIntSort(obj: unknown): boolean { + return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_INT_SORT; + } + + function isReal(obj: unknown): boolean { + return isArith(obj) && isRealSort(obj.sort); + } + + function isRealVal(obj: unknown): obj is RatNum { + const r = obj instanceof RatNumImpl; + r && _assertContext(obj); + return r; + } + + function isRealSort(obj: unknown): boolean { + return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT; + } + + function isBitVecSort(obj: unknown): obj is BitVecSort { + const r = obj instanceof BitVecSortImpl; + r && _assertContext(obj); + return r; + } + + function isBitVec(obj: unknown): obj is BitVec { + const r = obj instanceof BitVecImpl; + r && _assertContext(obj); + return r; + } + + function isBitVecVal(obj: unknown): obj is BitVecNum { + const r = obj instanceof BitVecNumImpl; + r && _assertContext(obj); + return r; + } + + function isProbe(obj: unknown): obj is Probe { + const r = obj instanceof ProbeImpl; + r && _assertContext(obj); + return r; + } + + function isTactic(obj: unknown): obj is Tactic { + const r = obj instanceof TacticImpl; + r && _assertContext(obj); + return r; + } + + function isAstVector(obj: unknown): obj is AstVector { + const r = obj instanceof AstVectorImpl; + r && _assertContext(obj); + return r; + } + + function eqIdentity(a: Ast, b: Ast): boolean { + return a.eqIdentity(b); + } + + function getVarIndex(obj: Expr): number { + assert(isVar(obj), 'Z3 bound variable expected'); + return Z3.get_index_value(contextPtr, obj.ast); + } + + function from(primitive: boolean): Bool; + function from(primitive: number): IntNum | RatNum; + function from(primitive: CoercibleRational): RatNum; + function from(primitive: bigint): IntNum; + function from>(expr: T): T; + function from(expr: CoercibleToExpr): AnyExpr; + function from(value: CoercibleToExpr): AnyExpr { + if (typeof value === 'boolean') { + return Bool.val(value); + } else if (typeof value === 'number') { + if (!Number.isFinite(value)) { + throw new Error(`cannot represent infinity/NaN (got ${value})`); + } + if (Math.floor(value) === value) { + return Int.val(value); + } + return Real.val(value); + } else if (isCoercibleRational(value)) { + return Real.val(value); + } else if (typeof value === 'bigint') { + return Int.val(value); + } else if (isExpr(value)) { + return value; } - assert(this.ctx.isConst(constant), 'Provided expression that is not a constant to addAndTrack'); - Z3.solver_assert_and_track(this.ctx.ptr, this.ptr, expr.ast, constant.ast); + assert(false); } - assertions(): AstVector { - return new AstVectorImpl(this.ctx, Z3.solver_get_assertions(this.ctx.ptr, this.ptr)); + async function solve(...assertions: Bool[]): Promise | 'unsat' | 'unknown'> { + const solver = new ctx.Solver(); + solver.add(...assertions); + const result = await solver.check(); + if (result === 'sat') { + return solver.model(); + } + return result; } - async check(...exprs: (Bool | AstVector)[]): Promise { - const assumptions = this.ctx._flattenArgs(exprs).map(expr => { - this.ctx._assertContext(expr); - return expr.ast; - }); - const result = await asyncMutex.runExclusive(() => - Z3.solver_check_assumptions(this.ctx.ptr, this.ptr, assumptions), + ///////////// + // Objects // + ///////////// + const Sort = { + declare: (name: string) => new SortImpl(Z3.mk_uninterpreted_sort(contextPtr, _toSymbol(name))), + }; + const Function = { + declare: (name: string, ...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr)); + }, + fresh: (...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_fresh_func_decl(contextPtr, 'f', dom, rng.ptr)); + }, + }; + const RecFunc = { + declare: (name: string, ...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_rec_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr)); + }, + + addDefinition: (f: FuncDecl, args: Expr[], body: Expr) => { + _assertContext(f, ...args, body); + check( + Z3.add_rec_def( + contextPtr, + f.ptr, + args.map(arg => arg.ast), + body.ast, + ), + ); + }, + }; + const Bool = { + sort: () => new BoolSortImpl(Z3.mk_bool_sort(contextPtr)), + + const: (name: string) => new BoolImpl(Z3.mk_const(contextPtr, _toSymbol(name), Bool.sort().ptr)), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Bool.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Bool.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'b') => new BoolImpl(Z3.mk_fresh_const(contextPtr, prefix, Bool.sort().ptr)), + + val: (value: boolean) => { + if (value) { + return new BoolImpl(Z3.mk_true(contextPtr)); + } + return new BoolImpl(Z3.mk_false(contextPtr)); + }, + }; + const Int = { + sort: () => new ArithSortImpl(Z3.mk_int_sort(contextPtr)), + + const: (name: string) => new ArithImpl(Z3.mk_const(contextPtr, _toSymbol(name), Int.sort().ptr)), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Int.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Int.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'x') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Int.sort().ptr)), + + val: (value: number | bigint | string) => { + assert(typeof value === 'bigint' || typeof value === 'string' || Number.isSafeInteger(value)); + return new IntNumImpl(check(Z3.mk_numeral(contextPtr, value.toString(), Int.sort().ptr))); + }, + }; + const Real = { + sort: () => new ArithSortImpl(Z3.mk_real_sort(contextPtr)), + + const: (name: string) => new ArithImpl(check(Z3.mk_const(contextPtr, _toSymbol(name), Real.sort().ptr))), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Real.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Real.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'b') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Real.sort().ptr)), + + val: (value: number | bigint | string | CoercibleRational) => { + if (isCoercibleRational(value)) { + value = `${value.numerator}/${value.denominator}`; + } + return new RatNumImpl(Z3.mk_numeral(contextPtr, value.toString(), Real.sort().ptr)); + }, + }; + const BitVec = { + sort(bits: Bits): BitVecSort { + assert(Number.isSafeInteger(bits), 'number of bits must be an integer'); + return new BitVecSortImpl(Z3.mk_bv_sort(contextPtr, bits)); + }, + + const(name: string, bits: Bits | BitVecSort): BitVec { + return new BitVecImpl( + check(Z3.mk_const(contextPtr, _toSymbol(name), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)), + ); + }, + + consts(names: string | string[], bits: Bits | BitVecSort): BitVec[] { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => BitVec.const(name, bits)); + }, + + val( + value: bigint | number | boolean, + bits: Bits | BitVecSort, + ): BitVecNum { + if (value === true) { + return BitVec.val(1, bits); + } else if (value === false) { + return BitVec.val(0, bits); + } + return new BitVecNumImpl( + check(Z3.mk_numeral(contextPtr, value.toString(), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)), + ); + }, + }; + + //////////////// + // Operations // + //////////////// + function If(condition: Probe, onTrue: Tactic, onFalse: Tactic): Tactic; + function If, OnFalseRef extends CoercibleToExpr>( + condition: Bool | boolean, + onTrue: OnTrueRef, + onFalse: OnFalseRef, + ): CoercibleToExprMap; + function If( + condition: Bool | Probe | boolean, + onTrue: CoercibleToExpr | Tactic, + onFalse: CoercibleToExpr | Tactic, + ): Expr | Tactic { + if (isProbe(condition) && isTactic(onTrue) && isTactic(onFalse)) { + return Cond(condition, onTrue, onFalse); + } + assert(!isProbe(condition) && !isTactic(onTrue) && !isTactic(onFalse), 'Mixed expressions and goals'); + if (typeof condition === 'boolean') { + condition = Bool.val(condition); + } + onTrue = from(onTrue); + onFalse = from(onFalse); + return _toExpr(check(Z3.mk_ite(contextPtr, condition.ptr, onTrue.ast, onFalse.ast))); + } + + function Distinct(...exprs: CoercibleToExpr[]): Bool { + assert(exprs.length > 0, "Can't make Distinct ouf of nothing"); + + return new BoolImpl( + check( + Z3.mk_distinct( + contextPtr, + exprs.map(expr => { + expr = from(expr); + _assertContext(expr); + return expr.ast; + }), + ), + ), ); - switch (result) { - case Z3_lbool.Z3_L_FALSE: - return unsat; - case Z3_lbool.Z3_L_TRUE: - return sat; - case Z3_lbool.Z3_L_UNDEF: - return unknown; - default: - assertExhaustive(result); + } + + function Const>(name: string, sort: S): SortToExprMap { + _assertContext(sort); + return _toExpr(check(Z3.mk_const(contextPtr, _toSymbol(name), sort.ptr))) as SortToExprMap; + } + + function Consts>(names: string | string[], sort: S): SortToExprMap[] { + _assertContext(sort); + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Const(name, sort)); + } + + function FreshConst>(sort: S, prefix: string = 'c'): SortToExprMap { + _assertContext(sort); + return _toExpr(Z3.mk_fresh_const(sort.ctx.ptr, prefix, sort.ptr)) as SortToExprMap; + } + + function Var>(idx: number, sort: S): SortToExprMap { + _assertContext(sort); + return _toExpr(Z3.mk_bound(sort.ctx.ptr, idx, sort.ptr)) as SortToExprMap; + } + + function Implies(a: Bool | boolean, b: Bool | boolean): Bool { + a = from(a) as Bool; + b = from(b) as Bool; + _assertContext(a, b); + return new BoolImpl(check(Z3.mk_implies(contextPtr, a.ptr, b.ptr))); + } + + function Eq(a: CoercibleToExpr, b: CoercibleToExpr): Bool { + a = from(a); + b = from(b); + _assertContext(a, b); + return a.eq(b); + } + + function Xor(a: Bool | boolean, b: Bool | boolean): Bool { + a = from(a) as Bool; + b = from(b) as Bool; + _assertContext(a, b); + return new BoolImpl(check(Z3.mk_xor(contextPtr, a.ptr, b.ptr))); + } + + function Not(a: Probe): Probe; + function Not(a: Bool | boolean): Bool; + function Not(a: Bool | boolean | Probe): Bool | Probe { + if (typeof a === 'boolean') { + a = from(a); + } + _assertContext(a); + if (isProbe(a)) { + return new ProbeImpl(check(Z3.probe_not(contextPtr, a.ptr))); + } + return new BoolImpl(check(Z3.mk_not(contextPtr, a.ptr))); + } + + function And(): Bool; + function And(vector: AstVector>): Bool; + function And(...args: (Bool | boolean)[]): Bool; + function And(...args: Probe[]): Probe; + function And( + ...args: (AstVector> | Probe | Bool | boolean)[] + ): Bool | Probe { + if (args.length == 1 && args[0] instanceof ctx.AstVector) { + args = [...args[0].values()]; + assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools'); + } + + const allProbes = allSatisfy(args, isProbe) ?? false; + if (allProbes) { + return _probeNary(Z3.probe_and, args as [Probe, ...Probe[]]); + } else { + const castArgs = args.map(from) as Bool[]; + _assertContext(...castArgs); + return new BoolImpl( + check( + Z3.mk_and( + contextPtr, + castArgs.map(arg => arg.ptr), + ), + ), + ); } } - model() { - return new this.ctx.Model(Z3.solver_get_model(this.ctx.ptr, this.ptr)); - } - } + function Or(): Bool; + function Or(vector: AstVector>): Bool; + function Or(...args: (Bool | boolean)[]): Bool; + function Or(...args: Probe[]): Probe; + function Or( + ...args: (AstVector> | Probe | Bool | boolean)[] + ): Bool | Probe { + if (args.length == 1 && args[0] instanceof ctx.AstVector) { + args = [...args[0].values()]; + assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools'); + } - class ModelImpl implements Model { - declare readonly __typename: Model['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_model = Z3.mk_model(ctx.ptr)) { - Z3.model_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.model_dec_ref(ctx.ptr, ptr)); - } - - get length() { - return Z3.model_get_num_consts(this.ctx.ptr, this.ptr) + Z3.model_get_num_funcs(this.ctx.ptr, this.ptr); - } - - [Symbol.iterator](): Iterator { - return this.values(); - } - - *entries(): IterableIterator<[number, FuncDecl]> { - const length = this.length; - for (let i = 0; i < length; i++) { - yield [i, this.get(i)]; + const allProbes = allSatisfy(args, isProbe) ?? false; + if (allProbes) { + return _probeNary(Z3.probe_or, args as [Probe, ...Probe[]]); + } else { + const castArgs = args.map(from) as Bool[]; + _assertContext(...castArgs); + return new BoolImpl( + check( + Z3.mk_or( + contextPtr, + castArgs.map(arg => arg.ptr), + ), + ), + ); } } - *keys(): IterableIterator { - for (const [key] of this.entries()) { - yield key; + function ToReal(expr: Arith | bigint): Arith { + expr = from(expr) as Arith; + _assertContext(expr); + assert(isInt(expr), 'Int expression expected'); + return new ArithImpl(check(Z3.mk_int2real(contextPtr, expr.ast))); + } + + function ToInt(expr: Arith | number | CoercibleRational | string): Arith { + if (!isExpr(expr)) { + expr = Real.val(expr); + } + _assertContext(expr); + assert(isReal(expr), 'Real expression expected'); + return new ArithImpl(check(Z3.mk_real2int(contextPtr, expr.ast))); + } + + function IsInt(expr: Arith | number | CoercibleRational | string): Bool { + if (!isExpr(expr)) { + expr = Real.val(expr); + } + _assertContext(expr); + assert(isReal(expr), 'Real expression expected'); + return new BoolImpl(check(Z3.mk_is_int(contextPtr, expr.ast))); + } + + function Sqrt(a: Arith | number | bigint | string | CoercibleRational): Arith { + if (!isExpr(a)) { + a = Real.val(a); + } + return a.pow('1/2'); + } + + function Cbrt(a: Arith | number | bigint | string | CoercibleRational): Arith { + if (!isExpr(a)) { + a = Real.val(a); + } + return a.pow('1/3'); + } + + function BV2Int(a: BitVec, isSigned: boolean): Arith { + _assertContext(a); + return new ArithImpl(check(Z3.mk_bv2int(contextPtr, a.ast, isSigned))); + } + + function Int2BV(a: Arith | bigint | number, bits: Bits): BitVec { + if (isArith(a)) { + assert(isInt(a), 'parameter must be an integer'); + } else { + assert(typeof a !== 'number' || Number.isSafeInteger(a), 'parameter must not have decimal places'); + a = Int.val(a); + } + return new BitVecImpl(check(Z3.mk_int2bv(contextPtr, bits, a.ast))); + } + + function Concat(...bitvecs: BitVec[]): BitVec { + _assertContext(...bitvecs); + return bitvecs.reduce((prev, curr) => new BitVecImpl(check(Z3.mk_concat(contextPtr, prev.ast, curr.ast)))); + } + + function Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic { + _assertContext(probe, onTrue, onFalse); + return new TacticImpl(check(Z3.tactic_cond(contextPtr, probe.ptr, onTrue.ptr, onFalse.ptr))); + } + + class AstImpl implements Ast { + declare readonly __typename: Ast['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Ptr) { + this.ctx = ctx; + const myAst = this.ast; + + Z3.inc_ref(contextPtr, myAst); + cleanup.register(this, () => Z3.dec_ref(contextPtr, myAst)); + } + + get ast(): Z3_ast { + return this.ptr; + } + + id() { + return Z3.get_ast_id(contextPtr, this.ast); + } + + eqIdentity(other: Ast) { + _assertContext(other); + return check(Z3.is_eq_ast(contextPtr, this.ast, other.ast)); + } + + neqIdentity(other: Ast) { + _assertContext(other); + return !this.eqIdentity(other); + } + + sexpr() { + return Z3.ast_to_string(contextPtr, this.ast); + } + + hash() { + return Z3.get_ast_hash(contextPtr, this.ast); + } + + toString() { + return this.sexpr(); } } - *values(): IterableIterator { - for (const [, value] of this.entries()) { - yield value; + class SolverImpl implements Solver { + declare readonly __typename: Solver['__typename']; + + readonly ptr: Z3_solver; + readonly ctx: Context; + constructor(ptr: Z3_solver | string = Z3.mk_solver(contextPtr)) { + this.ctx = ctx; + let myPtr: Z3_solver; + if (typeof ptr === 'string') { + myPtr = check(Z3.mk_solver_for_logic(contextPtr, _toSymbol(ptr))); + } else { + myPtr = ptr; + } + this.ptr = myPtr; + Z3.solver_inc_ref(contextPtr, myPtr); + cleanup.register(this, () => Z3.solver_dec_ref(contextPtr, myPtr)); + } + + push() { + Z3.solver_push(contextPtr, this.ptr); + } + pop(num: number = 1) { + Z3.solver_pop(contextPtr, this.ptr, num); + } + numScopes() { + return Z3.solver_get_num_scopes(contextPtr, this.ptr); + } + reset() { + Z3.solver_reset(contextPtr, this.ptr); + } + add(...exprs: (Bool | AstVector>)[]) { + _flattenArgs(exprs).forEach(expr => { + _assertContext(expr); + check(Z3.solver_assert(contextPtr, this.ptr, expr.ast)); + }); + } + addAndTrack(expr: Bool, constant: Bool | string) { + if (typeof constant === 'string') { + constant = Bool.const(constant); + } + assert(isConst(constant), 'Provided expression that is not a constant to addAndTrack'); + check(Z3.solver_assert_and_track(contextPtr, this.ptr, expr.ast, constant.ast)); + } + + assertions(): AstVector> { + return new AstVectorImpl(check(Z3.solver_get_assertions(contextPtr, this.ptr))); + } + + async check(...exprs: (Bool | AstVector>)[]): Promise { + const assumptions = _flattenArgs(exprs).map(expr => { + _assertContext(expr); + return expr.ast; + }); + const result = await asyncMutex.runExclusive(() => + check(Z3.solver_check_assumptions(contextPtr, this.ptr, assumptions)), + ); + switch (result) { + case Z3_lbool.Z3_L_FALSE: + return 'unsat'; + case Z3_lbool.Z3_L_TRUE: + return 'sat'; + case Z3_lbool.Z3_L_UNDEF: + return 'unknown'; + default: + assertExhaustive(result); + } + } + + model() { + return new ModelImpl(check(Z3.solver_get_model(contextPtr, this.ptr))); + } + + toString() { + return check(Z3.solver_to_string(contextPtr, this.ptr)); } } - decls() { - return [...this.values()]; - } + class ModelImpl implements Model { + declare readonly __typename: Model['__typename']; + readonly ctx: Context; - sexpr() { - return Z3.model_to_string(this.ctx.ptr, this.ptr); - } - - eval(expr: Bool, modelCompletion?: boolean): Bool; - eval(expr: Arith, modelCompletion?: boolean): Arith; - eval(expr: Expr, modelCompletion: boolean = false) { - this.ctx._assertContext(expr); - const r = Z3.model_eval(this.ctx.ptr, this.ptr, expr.ast, modelCompletion); - if (r === null) { - throw new Z3Error('Failed to evaluatio expression in the model'); + constructor(readonly ptr: Z3_model = Z3.mk_model(contextPtr)) { + this.ctx = ctx; + Z3.model_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.model_dec_ref(contextPtr, ptr)); + } + + length() { + return Z3.model_get_num_consts(contextPtr, this.ptr) + Z3.model_get_num_funcs(contextPtr, this.ptr); + } + + [Symbol.iterator](): Iterator> { + return this.values(); + } + + *entries(): IterableIterator<[number, FuncDecl]> { + const length = this.length(); + for (let i = 0; i < length; i++) { + yield [i, this.get(i)]; + } + } + + *keys(): IterableIterator { + for (const [key] of this.entries()) { + yield key; + } + } + + *values(): IterableIterator> { + for (const [, value] of this.entries()) { + yield value; + } + } + + decls() { + return [...this.values()]; + } + + sexpr() { + return check(Z3.model_to_string(contextPtr, this.ptr)); + } + + eval(expr: Bool, modelCompletion?: boolean): Bool; + eval(expr: Arith, modelCompletion?: boolean): Arith; + eval(expr: Expr, modelCompletion: boolean = false) { + _assertContext(expr); + const r = check(Z3.model_eval(contextPtr, this.ptr, expr.ast, modelCompletion)); + if (r === null) { + throw new Z3Error('Failed to evaluatio expression in the model'); + } + return _toExpr(r); + } + + get(i: number): FuncDecl; + get(from: number, to: number): FuncDecl[]; + get(declaration: FuncDecl): FuncInterp | Expr; + get(constant: Expr): Expr; + get(sort: Sort): AstVector>; + get( + i: number | FuncDecl | Expr | Sort, + to?: number, + ): FuncDecl | FuncInterp | Expr | AstVector> | FuncDecl[] { + assert(to === undefined || typeof i === 'number'); + if (typeof i === 'number') { + const length = this.length(); + + if (i >= length) { + throw new RangeError(`expected index ${i} to be less than length ${length}`); + } + + if (to === undefined) { + const numConsts = check(Z3.model_get_num_consts(contextPtr, this.ptr)); + if (i < numConsts) { + return new FuncDeclImpl(check(Z3.model_get_const_decl(contextPtr, this.ptr, i))); + } else { + return new FuncDeclImpl(check(Z3.model_get_func_decl(contextPtr, this.ptr, i - numConsts))); + } + } + + if (to < 0) { + to += length; + } + if (to >= length) { + throw new RangeError(`expected index ${to} to be less than length ${length}`); + } + const result = []; + for (let j = i; j < to; j++) { + result.push(this.get(j)); + } + return result; + } else if (isFuncDecl(i) || (isExpr(i) && isConst(i))) { + const result = this.getInterp(i); + assert(result !== null); + return result; + } else if (isSort(i)) { + return this.getUniverse(i); + } + assert(false, 'Number, declaration or constant expected'); + } + + private getInterp(expr: FuncDecl | Expr): Expr | FuncInterp | null { + assert(isFuncDecl(expr) || isConst(expr), 'Declaration expected'); + if (isConst(expr)) { + assert(isExpr(expr)); + expr = expr.decl(); + } + assert(isFuncDecl(expr)); + if (expr.arity() === 0) { + const result = check(Z3.model_get_const_interp(contextPtr, this.ptr, expr.ptr)); + if (result === null) { + return null; + } + return _toExpr(result); + } else { + const interp = check(Z3.model_get_func_interp(contextPtr, this.ptr, expr.ptr)); + if (interp === null) { + return null; + } + return new FuncInterpImpl(interp); + } + } + + private getUniverse(sort: Sort): AstVector> { + _assertContext(sort); + return new AstVectorImpl(check(Z3.model_get_sort_universe(contextPtr, this.ptr, sort.ptr))); } - return this.ctx._toExpr(r); } - get(i: number): FuncDecl; - get(from: number, to: number): FuncDecl[]; - get(declaration: FuncDecl): FuncInterp | Expr; - get(constant: Expr): Expr; - get(sort: Sort): AstVector; - get( - i: number | FuncDecl | Expr | Sort, - to?: number, - ): FuncDecl | FuncInterp | Expr | AstVector | FuncDecl[] { - assert(to === undefined || typeof i === 'number'); - if (typeof i === 'number') { - const length = this.length; + class FuncInterpImpl implements FuncInterp { + declare readonly __typename: FuncInterp['__typename']; + readonly ctx: Context; - if (i >= length) { - throw new RangeError(); + constructor(readonly ptr: Z3_func_interp) { + this.ctx = ctx; + Z3.func_interp_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.func_interp_dec_ref(contextPtr, ptr)); + } + } + + class SortImpl extends AstImpl implements Sort { + declare readonly __typename: Sort['__typename']; + + get ast(): Z3_ast { + return Z3.sort_to_ast(contextPtr, this.ptr); + } + + kind() { + return Z3.get_sort_kind(contextPtr, this.ptr); + } + + subsort(other: Sort) { + _assertContext(other); + return false; + } + + cast(expr: Expr): Expr { + _assertContext(expr); + assert(expr.sort.eqIdentity(expr.sort), 'Sort mismatch'); + return expr; + } + + name() { + return _fromSymbol(Z3.get_sort_name(contextPtr, this.ptr)); + } + + eqIdentity(other: Sort) { + _assertContext(other); + return check(Z3.is_eq_sort(contextPtr, this.ptr, other.ptr)); + } + + neqIdentity(other: Sort) { + return !this.eqIdentity(other); + } + } + + class FuncDeclImpl extends AstImpl implements FuncDecl { + declare readonly __typename: FuncDecl['__typename']; + + get ast() { + return Z3.func_decl_to_ast(contextPtr, this.ptr); + } + + name() { + return _fromSymbol(Z3.get_decl_name(contextPtr, this.ptr)); + } + + arity() { + return Z3.get_arity(contextPtr, this.ptr); + } + + domain(i: number) { + assert(i < this.arity(), 'Index out of bounds'); + return _toSort(Z3.get_domain(contextPtr, this.ptr, i)); + } + + range() { + return _toSort(Z3.get_range(contextPtr, this.ptr)); + } + + kind() { + return Z3.get_decl_kind(contextPtr, this.ptr); + } + + params(): (number | string | Z3_symbol | Sort | Expr | FuncDecl)[] { + const n = Z3.get_decl_num_parameters(contextPtr, this.ptr); + const result = []; + for (let i = 0; i < n; i++) { + const kind = check(Z3.get_decl_parameter_kind(contextPtr, this.ptr, i)); + switch (kind) { + case Z3_parameter_kind.Z3_PARAMETER_INT: + result.push(check(Z3.get_decl_int_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_DOUBLE: + result.push(check(Z3.get_decl_double_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_RATIONAL: + result.push(check(Z3.get_decl_rational_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_SYMBOL: + result.push(check(Z3.get_decl_symbol_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_SORT: + result.push(new SortImpl(check(Z3.get_decl_sort_parameter(contextPtr, this.ptr, i)))); + break; + case Z3_parameter_kind.Z3_PARAMETER_AST: + result.push(new ExprImpl(check(Z3.get_decl_ast_parameter(contextPtr, this.ptr, i)))); + break; + case Z3_parameter_kind.Z3_PARAMETER_FUNC_DECL: + result.push(new FuncDeclImpl(check(Z3.get_decl_func_decl_parameter(contextPtr, this.ptr, i)))); + break; + default: + assertExhaustive(kind); + } + } + return result; + } + + call(...args: CoercibleToExpr[]) { + assert(args.length === this.arity(), `Incorrect number of arguments to ${this}`); + return _toExpr( + check(Z3.mk_app( + contextPtr, + this.ptr, + args.map((arg, i) => { + return this.domain(i).cast(arg).ast; + }), + )), + ); + } + } + + class ExprImpl = AnySort> extends AstImpl implements Expr { + declare readonly __typename: Expr['__typename']; + + get sort(): S { + return _toSort(Z3.get_sort(contextPtr, this.ast)) as S; + } + + eq(other: CoercibleToExpr): Bool { + return new BoolImpl(check(Z3.mk_eq(contextPtr, this.ast, from(other).ast))); + } + + neq(other: CoercibleToExpr): Bool { + return new BoolImpl( + check(Z3.mk_distinct( + contextPtr, + [this, other].map(expr => from(expr).ast), + )), + ); + } + + params() { + return this.decl().params(); + } + + decl(): FuncDecl { + assert(isApp(this), 'Z3 application expected'); + return new FuncDeclImpl(check(Z3.get_app_decl(contextPtr, check(Z3.to_app(contextPtr, this.ast))))); + } + + numArgs(): number { + assert(isApp(this), 'Z3 applicaiton expected'); + return check(Z3.get_app_num_args(contextPtr, check(Z3.to_app(contextPtr, this.ast)))); + } + + arg(i: number): ReturnType { + assert(isApp(this), 'Z3 applicaiton expected'); + assert(i < this.numArgs(), `Invalid argument index - expected ${i} to be less than ${this.numArgs()}`); + return _toExpr(check(Z3.get_app_arg(contextPtr, check(Z3.to_app(contextPtr, this.ast)), i))); + } + + children(): ReturnType[] { + const num_args = this.numArgs(); + if (isApp(this)) { + const result = []; + for (let i = 0; i < num_args; i++) { + result.push(this.arg(i)); + } + return result; + } + return []; + } + } + + class BoolSortImpl extends SortImpl implements BoolSort { + declare readonly __typename: BoolSort['__typename']; + + cast(other: Bool | boolean): Bool; + cast(other: CoercibleToExpr): never; + cast(other: CoercibleToExpr | Bool) { + if (typeof other === 'boolean') { + other = Bool.val(other); + } + assert(isExpr(other), 'true, false or Z3 Boolean expression expected.'); + assert(this.eqIdentity(other.sort), 'Value cannot be converted into a Z3 Boolean value'); + return other; + } + + subsort(other: Sort) { + _assertContext(other.ctx); + return other instanceof ArithSortImpl; + } + } + + class BoolImpl extends ExprImpl> implements Bool { + declare readonly __typename: Bool['__typename']; + + not(): Bool { + return Not(this); + } + and(other: Bool | boolean): Bool { + return And(this, other); + } + or(other: Bool | boolean): Bool { + return Or(this, other); + } + xor(other: Bool | boolean): Bool { + return Xor(this, other); + } + } + + class ProbeImpl implements Probe { + declare readonly __typename: Probe['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Z3_probe) { + this.ctx = ctx; + } + } + + class TacticImpl implements Tactic { + declare readonly __typename: Tactic['__typename']; + + readonly ptr: Z3_tactic; + readonly ctx: Context; + + constructor(tactic: string | Z3_tactic) { + this.ctx = ctx; + let myPtr: Z3_tactic; + if (typeof tactic === 'string') { + myPtr = check(Z3.mk_tactic(contextPtr, tactic)); + } else { + myPtr = tactic; + } + + this.ptr = myPtr; + + Z3.tactic_inc_ref(contextPtr, myPtr); + cleanup.register(this, () => Z3.tactic_dec_ref(contextPtr, myPtr)); + } + } + + class ArithSortImpl extends SortImpl implements ArithSort { + declare readonly __typename: ArithSort['__typename']; + + cast(other: bigint | number): IntNum | RatNum; + cast(other: CoercibleRational | RatNum): RatNum; + cast(other: IntNum): IntNum; + cast(other: Bool | Arith): Arith; + cast(other: CoercibleToExpr): never; + cast(other: CoercibleToExpr): Arith | RatNum | IntNum { + const sortTypeStr = isIntSort(this) ? 'IntSort' : 'RealSort'; + if (isExpr(other)) { + const otherS = other.sort; + if (isArith(other)) { + if (this.eqIdentity(otherS)) { + return other; + } else if (isIntSort(otherS) && isRealSort(this)) { + return ToReal(other); + } + assert(false, "Can't cast Real to IntSort without loss"); + } else if (isBool(other)) { + if (isIntSort(this)) { + return If(other, 1, 0); + } else { + return ToReal(If(other, 1, 0)); + } + } + assert(false, `Can't cast expression to ${sortTypeStr}`); + } else { + if (typeof other !== 'boolean') { + if (isIntSort(this)) { + assert(!isCoercibleRational(other), "Can't cast fraction to IntSort"); + return Int.val(other); + } + return Real.val(other); + } + assert(false, `Can't cast primitive to ${sortTypeStr}`); + } + } + } + + class ArithImpl extends ExprImpl> implements Arith { + declare readonly __typename: Arith['__typename']; + + add(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_add(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + mul(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_mul(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + sub(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_sub(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + pow(exponent: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_power(contextPtr, this.ast, this.sort.cast(exponent).ast))); + } + + div(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_div(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + mod(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_mod(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + neg() { + return new ArithImpl(check(Z3.mk_unary_minus(contextPtr, this.ast))); + } + + le(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_le(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + lt(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_lt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + gt(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_gt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + ge(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_ge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + } + + class IntNumImpl extends ArithImpl implements IntNum { + declare readonly __typename: IntNum['__typename']; + + value() { + return BigInt(this.asString()); + } + + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + + asBinary() { + return Z3.get_numeral_binary_string(contextPtr, this.ast); + } + } + + class RatNumImpl extends ArithImpl implements RatNum { + declare readonly __typename: RatNum['__typename']; + + value() { + return { numerator: this.numerator().value(), denominator: this.denominator().value() }; + } + + numerator() { + return new IntNumImpl(Z3.get_numerator(contextPtr, this.ast)); + } + + denominator() { + return new IntNumImpl(Z3.get_denominator(contextPtr, this.ast)); + } + + asNumber() { + const { numerator, denominator } = this.value(); + const div = numerator / denominator; + return Number(div) + Number(numerator - div * denominator) / Number(denominator); + } + + asDecimal(prec: number = Number.parseInt(getParam('precision') ?? FALLBACK_PRECISION.toString())) { + return Z3.get_numeral_decimal_string(contextPtr, this.ast, prec); + } + + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + } + + class BitVecSortImpl extends SortImpl implements BitVecSort { + declare readonly __typename: BitVecSort['__typename']; + + size() { + return Z3.get_bv_sort_size(contextPtr, this.ptr) as Bits; + } + + subsort(other: Sort): boolean { + return isBitVecSort(other) && this.size() < other.size(); + } + + cast(other: CoercibleToBitVec): BitVec; + cast(other: CoercibleToExpr): Expr; + cast(other: CoercibleToExpr): Expr { + if (isExpr(other)) { + _assertContext(other); + return other; + } + assert(!isCoercibleRational(other), "Can't convert rational to BitVec"); + return BitVec.val(other, this.size()); + } + } + + class BitVecImpl extends ExprImpl> implements BitVec { + declare readonly __typename: BitVec['__typename']; + + size() { + return this.sort.size(); + } + + add(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvadd(contextPtr, this.ast, this.sort.cast(other).ast))); + } + mul(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvmul(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sub(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsub(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sdiv(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsdiv(contextPtr, this.ast, this.sort.cast(other).ast))); + } + udiv(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvudiv(contextPtr, this.ast, this.sort.cast(other).ast))); + } + smod(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsmod(contextPtr, this.ast, this.sort.cast(other).ast))); + } + urem(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvurem(contextPtr, this.ast, this.sort.cast(other).ast))); + } + srem(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsrem(contextPtr, this.ast, this.sort.cast(other).ast))); + } + neg(): BitVec { + return new BitVecImpl(check(Z3.mk_bvneg(contextPtr, this.ast))); + } + + or(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + and(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvand(contextPtr, this.ast, this.sort.cast(other).ast))); + } + nand(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvnand(contextPtr, this.ast, this.sort.cast(other).ast))); + } + xor(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvxor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + xnor(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvxnor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + shr(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvashr(contextPtr, this.ast, this.sort.cast(count).ast))); + } + lshr(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvlshr(contextPtr, this.ast, this.sort.cast(count).ast))); + } + shl(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvshl(contextPtr, this.ast, this.sort.cast(count).ast))); + } + rotateRight(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_ext_rotate_right(contextPtr, this.ast, this.sort.cast(count).ast))); + } + rotateLeft(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_ext_rotate_left(contextPtr, this.ast, this.sort.cast(count).ast))); + } + not(): BitVec { + return new BitVecImpl(check(Z3.mk_bvnot(contextPtr, this.ast))); + } + + extract(high: number, low: number): BitVec { + return new BitVecImpl(check(Z3.mk_extract(contextPtr, high, low, this.ast))); + } + signExt(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_sign_ext(contextPtr, count, this.ast))); + } + zeroExt(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_zero_ext(contextPtr, count, this.ast))); + } + repeat(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_repeat(contextPtr, count, this.ast))); + } + + sle(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsle(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ule(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvule(contextPtr, this.ast, this.sort.cast(other).ast))); + } + slt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvslt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ult(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvult(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sge(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + uge(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvuge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sgt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsgt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ugt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvugt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + redAnd(): BitVec { + return new BitVecImpl(check(Z3.mk_bvredand(contextPtr, this.ast))); + } + redOr(): BitVec { + return new BitVecImpl(check(Z3.mk_bvredor(contextPtr, this.ast))); + } + + addNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvadd_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + addNoUnderflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvadd_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + subNoOverflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsub_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + subNoUndeflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvsub_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + sdivNoOverflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsdiv_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + mulNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvmul_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + mulNoUndeflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvmul_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + negNoOverflow(): Bool { + return new BoolImpl(check(Z3.mk_bvneg_no_overflow(contextPtr, this.ast))); + } + } + + class BitVecNumImpl extends BitVecImpl implements BitVecNum { + declare readonly __typename: BitVecNum['__typename']; + value() { + return BigInt(this.asString()); + } + + asSignedValue() { + let val = this.value(); + const size = BigInt(this.size()); + if (val >= 2n ** (size - 1n)) { + val = val - 2n ** size; + } + if (val < (-2n) ** (size - 1n)) { + val = val + 2n ** size; + } + return val; + } + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + asBinaryString() { + return Z3.get_numeral_binary_string(contextPtr, this.ast); + } + } + + class AstVectorImpl> { + declare readonly __typename: AstVector['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Z3_ast_vector = Z3.mk_ast_vector(contextPtr)) { + this.ctx = ctx; + Z3.ast_vector_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.ast_vector_dec_ref(contextPtr, ptr)); + } + + length(): number { + return Z3.ast_vector_size(contextPtr, this.ptr); + } + + [Symbol.iterator](): IterableIterator { + return this.values(); + } + + *entries(): IterableIterator<[number, Item]> { + const length = this.length(); + for (let i = 0; i < length; i++) { + yield [i, this.get(i)]; + } + } + + *keys(): IterableIterator { + for (let [key] of this.entries()) { + yield key; + } + } + + *values(): IterableIterator { + for (let [, value] of this.entries()) { + yield value; + } + } + + get(i: number): Item; + get(from: number, to: number): Item[]; + get(from: number, to?: number): Item | Item[] { + const length = this.length(); + if (from < 0) { + from += length; + } + if (from >= length) { + throw new RangeError(`expected from index ${from} to be less than length ${length}`); } if (to === undefined) { - const numConsts = Z3.model_get_num_consts(this.ctx.ptr, this.ptr); - if (i < numConsts) { - return new FuncDeclImpl(this.ctx, Z3.model_get_const_decl(this.ctx.ptr, this.ptr, i)); - } else { - return new FuncDeclImpl(this.ctx, Z3.model_get_func_decl(this.ctx.ptr, this.ptr, i - numConsts)); - } + return _toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, from))) as Item; } if (to < 0) { to += length; } if (to >= length) { - throw new RangeError(); + throw new RangeError(`expected to index ${to} to be less than length ${length}`); } - const result = []; - for (let j = i; j < to; j++) { - result.push(this.get(j)); - } - return result; - } else if (this.ctx.isFuncDecl(i) || (this.ctx.isExpr(i) && this.ctx.isConst(i))) { - const result = this.getInterp(i); - assert(result !== null); - return result; - } else if (this.ctx.isSort(i)) { - return this.getUniverse(i); - } - assert(false, 'Number, declaration or constant expected'); - } - private getInterp(expr: FuncDecl | Expr): Expr | FuncInterp | null { - assert(this.ctx.isFuncDecl(expr) || this.ctx.isConst(expr), 'Declaration expected'); - if (this.ctx.isConst(expr)) { - assert(this.ctx.isExpr(expr)); - expr = expr.decl(); - } - assert(this.ctx.isFuncDecl(expr)); - if (expr.arity() === 0) { - const result = Z3.model_get_const_interp(this.ctx.ptr, this.ptr, expr.ptr); - if (result === null) { - return null; - } - return this.ctx._toExpr(result); - } else { - const interp = Z3.model_get_func_interp(this.ctx.ptr, this.ptr, expr.ptr); - if (interp === null) { - return null; - } - return new FuncInterpImpl(this.ctx, interp); - } - } - - private getUniverse(sort: Sort): AstVector { - this.ctx._assertContext(sort); - return new AstVectorImpl(this.ctx, Z3.model_get_sort_universe(this.ctx.ptr, this.ptr, sort.ptr)); - } - } - - class FuncInterpImpl implements FuncInterp { - declare readonly __typename: FuncInterp['__typename']; - - constructor(readonly ctx: Context, readonly ptr: Z3_func_interp) { - Z3.func_interp_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.func_interp_dec_ref(ctx.ptr, ptr)); - } - } - - class SortImpl extends AstImpl implements Sort { - declare readonly __typename: Sort['__typename']; - - get ast(): Z3_ast { - return Z3.sort_to_ast(this.ctx.ptr, this.ptr); - } - - kind() { - return Z3.get_sort_kind(this.ctx.ptr, this.ptr); - } - - subsort(other: Sort) { - this.ctx._assertContext(other); - return false; - } - - cast(expr: Expr): Expr { - this.ctx._assertContext(expr); - assert(expr.sort.eqIdentity(expr.sort), 'Sort mismatch'); - return expr; - } - - name() { - return this.ctx._fromSymbol(Z3.get_sort_name(this.ctx.ptr, this.ptr)); - } - - eqIdentity(other: Sort) { - this.ctx._assertContext(other); - return Z3.is_eq_sort(this.ctx.ptr, this.ptr, other.ptr); - } - - neqIdentity(other: Sort) { - return !this.eqIdentity(other); - } - } - - class FuncDeclImpl extends AstImpl implements FuncDecl { - declare readonly __typename: FuncDecl['__typename']; - - get ast() { - return Z3.func_decl_to_ast(this.ctx.ptr, this.ptr); - } - - name() { - return this.ctx._fromSymbol(Z3.get_decl_name(this.ctx.ptr, this.ptr)); - } - - arity() { - return Z3.get_arity(this.ctx.ptr, this.ptr); - } - - domain(i: number) { - assert(i < this.arity(), 'Index out of bounds'); - return this.ctx._toSort(Z3.get_domain(this.ctx.ptr, this.ptr, i)); - } - - range() { - return this.ctx._toSort(Z3.get_range(this.ctx.ptr, this.ptr)); - } - - kind() { - return Z3.get_decl_kind(this.ctx.ptr, this.ptr); - } - - params(): (number | string | Z3_symbol | Sort | Expr | FuncDecl)[] { - const n = Z3.get_decl_num_parameters(this.ctx.ptr, this.ptr); - const result = []; - for (let i = 0; i < n; i++) { - const kind = Z3.get_decl_parameter_kind(this.ctx.ptr, this.ptr, i); - switch (kind) { - case Z3_parameter_kind.Z3_PARAMETER_INT: - result.push(Z3.get_decl_int_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_DOUBLE: - result.push(Z3.get_decl_double_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_RATIONAL: - result.push(Z3.get_decl_rational_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_SYMBOL: - result.push(Z3.get_decl_symbol_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_SORT: - result.push(new SortImpl(this.ctx, Z3.get_decl_sort_parameter(this.ctx.ptr, this.ptr, i))); - break; - case Z3_parameter_kind.Z3_PARAMETER_AST: - result.push(new ExprImpl(this.ctx, Z3.get_decl_ast_parameter(this.ctx.ptr, this.ptr, i))); - break; - case Z3_parameter_kind.Z3_PARAMETER_FUNC_DECL: - result.push(new FuncDeclImpl(this.ctx, Z3.get_decl_func_decl_parameter(this.ctx.ptr, this.ptr, i))); - break; - default: - assertExhaustive(kind); - } - } - return result; - } - - call(...args: CoercibleToExpr[]) { - assert(args.length === this.arity(), `Incorrect number of arguments to ${this}`); - return this.ctx._toExpr( - Z3.mk_app( - this.ctx.ptr, - this.ptr, - args.map((arg, i) => { - return this.domain(i).cast(arg).ast; - }), - ), - ); - } - } - - class ExprImpl extends AstImpl implements Expr { - declare readonly __typename: Expr['__typename']; - - get sort(): S { - return this.ctx._toSort(Z3.get_sort(this.ctx.ptr, this.ast)) as S; - } - - eq(other: CoercibleToExpr): Bool { - return new BoolImpl(this.ctx, Z3.mk_eq(this.ctx.ptr, this.ast, this.ctx.from(other).ast)); - } - - neq(other: CoercibleToExpr): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_distinct( - this.ctx.ptr, - [this, other].map(expr => this.ctx.from(expr).ast), - ), - ); - } - - params() { - return this.decl().params(); - } - - decl(): FuncDecl { - assert(this.ctx.isApp(this), 'Z3 application expected'); - return new FuncDeclImpl(this.ctx, Z3.get_app_decl(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast))); - } - - numArgs(): number { - assert(this.ctx.isApp(this), 'Z3 applicaiton expected'); - return Z3.get_app_num_args(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast)); - } - - arg(i: number): ReturnType { - assert(this.ctx.isApp(this), 'Z3 applicaiton expected'); - assert(i < this.numArgs(), 'Invalid argument index'); - return this.ctx._toExpr(Z3.get_app_arg(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast), i)); - } - - children(): ReturnType[] { - const num_args = this.numArgs(); - if (this.ctx.isApp(this)) { - const result = []; - for (let i = 0; i < num_args; i++) { - result.push(this.arg(i)); + const result: Item[] = []; + for (let i = from; i < to; i++) { + result.push(_toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, i))) as Item); } return result; } - return []; - } - } - class BoolSortImpl extends SortImpl implements BoolSort { - declare readonly __typename: BoolSort['__typename']; - - cast(other: Bool | boolean): Bool; - cast(other: CoercibleToExpr): never; - cast(other: CoercibleToExpr | Bool) { - if (typeof other === 'boolean') { - other = this.ctx.Bool.val(other); - } - assert(this.ctx.isExpr(other), 'true, false or Z3 Boolean expression expected.'); - assert(this.eqIdentity(other.sort), 'Value cannot be converted into a Z3 Boolean value'); - return other; - } - - subsort(other: Sort) { - this.ctx._assertContext(other.ctx); - return other instanceof ArithSortImpl; - } - } - - class BoolImpl extends ExprImpl implements Bool { - declare readonly __typename: Bool['__typename']; - - not(): Bool { - return this.ctx.Not(this); - } - and(other: Bool | boolean): Bool { - return this.ctx.And(this, other); - } - or(other: Bool | boolean): Bool { - return this.ctx.Or(this, other); - } - xor(other: Bool | boolean): Bool { - return this.ctx.Xor(this, other); - } - } - - class ProbeImpl implements Probe { - declare readonly __typename: Probe['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_probe) {} - } - - class TacticImpl implements Tactic { - declare readonly __typename: Tactic['__typename']; - - readonly ptr: Z3_tactic; - - constructor(readonly ctx: ContextImpl, tactic: string | Z3_tactic) { - let myPtr: Z3_tactic; - if (typeof tactic === 'string') { - myPtr = Z3.mk_tactic(ctx.ptr, tactic); - } else { - myPtr = tactic; + set(i: number, v: Item): void { + _assertContext(v); + if (i >= this.length()) { + throw new RangeError(`expected index ${i} to be less than length ${this.length()}`); + } + check(Z3.ast_vector_set(contextPtr, this.ptr, i, v.ast)); } - this.ptr = myPtr; + push(v: Item): void { + _assertContext(v); + check(Z3.ast_vector_push(contextPtr, this.ptr, v.ast)); + } - Z3.tactic_inc_ref(ctx.ptr, myPtr); - cleanup.register(this, () => Z3.tactic_dec_ref(ctx.ptr, myPtr)); - } - } + resize(size: number): void { + check(Z3.ast_vector_resize(contextPtr, this.ptr, size)); + } - class ArithSortImpl extends SortImpl implements ArithSort { - declare readonly __typename: ArithSort['__typename']; - - cast(other: bigint | number): IntNum | RatNum; - cast(other: CoercibleRational | RatNum): RatNum; - cast(other: IntNum): IntNum; - cast(other: Bool | Arith): Arith; - cast(other: CoercibleToExpr): never; - cast(other: CoercibleToExpr): Arith | RatNum | IntNum { - const { If, isExpr, isArith, isBool, isIntSort, isRealSort, ToReal, Int, Real } = this.ctx; - const sortTypeStr = isIntSort(this) ? 'IntSort' : 'RealSort'; - if (isExpr(other)) { - const otherS = other.sort; - if (isArith(other)) { - if (this.eqIdentity(otherS)) { - return other; - } else if (isIntSort(otherS) && isRealSort(this)) { - return this.ctx.ToReal(other); - } - assert(false, "Can't cast Real to IntSort without loss"); - } else if (isBool(other)) { - if (isIntSort(this)) { - return If(other, 1, 0); - } else { - return ToReal(If(other, 1, 0)); + has(v: Item): boolean { + _assertContext(v); + for (const item of this.values()) { + if (item.eqIdentity(v)) { + return true; } } - assert(false, `Can't cast expression to ${sortTypeStr}`); - } else { - if (typeof other !== 'boolean') { - if (isIntSort(this)) { - assert(!isCoercibleRational(other), "Can't cast fraction to IntSort"); - return Int.val(other); - } - return Real.val(other); - } - assert(false, `Can't cast primitive to ${sortTypeStr}`); + return false; } - } - } - class ArithImpl extends ExprImpl implements Arith { - declare readonly __typename: Arith['__typename']; - - add(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_add(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - mul(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_mul(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - sub(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_sub(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - pow(exponent: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_power(this.ctx.ptr, this.ast, this.sort.cast(exponent).ast)); - } - - div(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_div(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - mod(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_mod(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - neg() { - return new ArithImpl(this.ctx, Z3.mk_unary_minus(this.ctx.ptr, this.ast)); - } - - le(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_le(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - lt(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_lt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - gt(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_gt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - ge(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_ge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - } - - class IntNumImpl extends ArithImpl implements IntNum { - declare readonly __typename: IntNum['__typename']; - - get value() { - return BigInt(this.asString()); - } - - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - - asBinary() { - return Z3.get_numeral_binary_string(this.ctx.ptr, this.ast); - } - } - - class RatNumImpl extends ArithImpl implements RatNum { - declare readonly __typename: RatNum['__typename']; - - get value() { - return { numerator: this.numerator().value, denominator: this.denominator().value }; - } - - numerator() { - return new IntNumImpl(this.ctx, Z3.get_numerator(this.ctx.ptr, this.ast)); - } - - denominator() { - return new IntNumImpl(this.ctx, Z3.get_denominator(this.ctx.ptr, this.ast)); - } - - asNumber() { - const { numerator, denominator } = this.value; - const div = numerator / denominator; - return Number(div) + Number(numerator - div * denominator) / Number(denominator); - } - - asDecimal(prec: number = Number.parseInt(getParam('precision') ?? FALLBACK_PRECISION.toString())) { - return Z3.get_numeral_decimal_string(this.ctx.ptr, this.ast, prec); - } - - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - } - - class BitVecSortImpl extends SortImpl implements BitVecSort { - declare readonly __typename: BitVecSort['__typename']; - - get size() { - return Z3.get_bv_sort_size(this.ctx.ptr, this.ptr); - } - - subsort(other: Sort): boolean { - return this.ctx.isBitVecSort(other) && this.size < other.size; - } - - cast(other: CoercibleToBitVec): BitVec; - cast(other: CoercibleToExpr): Expr; - cast(other: CoercibleToExpr): Expr { - if (this.ctx.isExpr(other)) { - this.ctx._assertContext(other); - return other; - } - assert(!isCoercibleRational(other), "Can't convert rational to BitVec"); - return this.ctx.BitVec.val(other, this.size); - } - } - - class BitVecImpl extends ExprImpl implements BitVec { - declare readonly __typename: BitVec['__typename']; - - get size() { - return this.sort.size; - } - - add(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvadd(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - mul(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvmul(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sub(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsub(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sdiv(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsdiv(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - udiv(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvudiv(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - smod(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsmod(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - urem(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvurem(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - srem(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsrem(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - neg(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvneg(this.ctx.ptr, this.ast)); - } - - or(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - and(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvand(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - nand(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvnand(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - xor(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvxor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - xnor(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvxnor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - shr(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvashr(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - lshr(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvlshr(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - shl(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvshl(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - rotateRight(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_ext_rotate_right(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - rotateLeft(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_ext_rotate_left(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - not(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvnot(this.ctx.ptr, this.ast)); - } - - extract(high: number, low: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_extract(this.ctx.ptr, high, low, this.ast)); - } - signExt(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_sign_ext(this.ctx.ptr, count, this.ast)); - } - zeroExt(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_zero_ext(this.ctx.ptr, count, this.ast)); - } - repeat(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_repeat(this.ctx.ptr, count, this.ast)); - } - - sle(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsle(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ule(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvule(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - slt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvslt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ult(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvult(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sge(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - uge(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvuge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sgt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsgt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ugt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvugt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - redAnd(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvredand(this.ctx.ptr, this.ast)); - } - redOr(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvredor(this.ctx.ptr, this.ast)); - } - - addNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvadd_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - addNoUnderflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvadd_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - subNoOverflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsub_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - subNoUndeflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvsub_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - sdivNoOverflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsdiv_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - mulNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvmul_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - mulNoUndeflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvmul_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - negNoOverflow(): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvneg_no_overflow(this.ctx.ptr, this.ast)); - } - } - - class BitVecNumImpl extends BitVecImpl implements BitVecNum { - declare readonly __typename: BitVecNum['__typename']; - get value() { - return BigInt(this.asString()); - } - - asSignedValue() { - let val = this.value; - const size = BigInt(this.size); - if (val >= 2n ** (size - 1n)) { - val = val - 2n ** size; - } - if (val < (-2n) ** (size - 1n)) { - val = val + 2n ** size; - } - return val; - } - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - asBinaryString() { - return Z3.get_numeral_binary_string(this.ctx.ptr, this.ast); - } - } - - class AstVectorImpl { - declare readonly __typename: AstVector['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_ast_vector = Z3.mk_ast_vector(ctx.ptr)) { - Z3.ast_vector_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.ast_vector_dec_ref(ctx.ptr, ptr)); - } - - get length(): number { - return Z3.ast_vector_size(this.ctx.ptr, this.ptr); - } - - [Symbol.iterator](): IterableIterator { - return this.values(); - } - - *entries(): IterableIterator<[number, Item]> { - const length = this.length; - for (let i = 0; i < length; i++) { - yield [i, this.get(i)]; + sexpr(): string { + return check(Z3.ast_vector_to_string(contextPtr, this.ptr)); } } - *keys(): IterableIterator { - for (let [key] of this.entries()) { - yield key; - } - } + class AstMapImpl, Value extends AnyAst> implements AstMap { + declare readonly __typename: AstMap['__typename']; + readonly ctx: Context; - *values(): IterableIterator { - for (let [, value] of this.entries()) { - yield value; - } - } - - get(i: number): Item; - get(from: number, to: number): Item[]; - get(from: number, to?: number): Item | Item[] { - const length = this.length; - if (from < 0) { - from += length; - } - if (from >= length) { - throw new RangeError(); + constructor(readonly ptr: Z3_ast_map = Z3.mk_ast_map(contextPtr)) { + this.ctx = ctx; + Z3.ast_map_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.ast_map_dec_ref(contextPtr, ptr)); } - if (to === undefined) { - return this.ctx._toAst(Z3.ast_vector_get(this.ctx.ptr, this.ptr, from)) as Item; + [Symbol.iterator](): Iterator<[Key, Value]> { + return this.entries(); } - if (to < 0) { - to += length; - } - if (to >= length) { - throw new RangeError(); + get size(): number { + return Z3.ast_map_size(contextPtr, this.ptr); } - const result: Item[] = []; - for (let i = from; i < to; i++) { - result.push(this.ctx._toAst(Z3.ast_vector_get(this.ctx.ptr, this.ptr, i)) as Item); - } - return result; - } - - set(i: number, v: Item): void { - this.ctx._assertContext(v); - if (i >= this.length) { - throw new RangeError(); - } - Z3.ast_vector_set(this.ctx.ptr, this.ptr, i, v.ast); - } - - push(v: Item): void { - this.ctx._assertContext(v); - Z3.ast_vector_push(this.ctx.ptr, this.ptr, v.ast); - } - - resize(size: number): void { - Z3.ast_vector_resize(this.ctx.ptr, this.ptr, size); - } - - has(v: Item): boolean { - this.ctx._assertContext(v); - for (const item of this.values()) { - if (item.eqIdentity(v)) { - return true; + *entries(): IterableIterator<[Key, Value]> { + for (const key of this.keys()) { + yield [key, this.get(key)]; } } - return false; - } - sexpr(): string { - return Z3.ast_vector_to_string(this.ctx.ptr, this.ptr); - } - } + keys(): AstVector { + return new AstVectorImpl(Z3.ast_map_keys(contextPtr, this.ptr)); + } - class AstMapImpl implements AstMap { - declare readonly __typename: AstMap['__typename']; + *values(): IterableIterator { + for (const [_, value] of this.entries()) { + yield value; + } + } + get(key: Key): Value { + return _toAst(check(Z3.ast_map_find(contextPtr, this.ptr, key.ast))) as Value; + } - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_ast_map = Z3.mk_ast_map(ctx.ptr)) { - Z3.ast_map_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.ast_map_dec_ref(ctx.ptr, ptr)); - } + set(key: Key, value: Value): void { + check(Z3.ast_map_insert(contextPtr, this.ptr, key.ast, value.ast)); + } - [Symbol.iterator](): Iterator<[Key, Value]> { - return this.entries(); - } + delete(key: Key): void { + check(Z3.ast_map_erase(contextPtr, this.ptr, key.ast)); + } - get size(): number { - return Z3.ast_map_size(this.ctx.ptr, this.ptr); - } + clear(): void { + check(Z3.ast_map_reset(contextPtr, this.ptr)); + } - *entries(): IterableIterator<[Key, Value]> { - for (const key of this.keys()) { - yield [key, this.get(key)]; + has(key: Key): boolean { + return check(Z3.ast_map_contains(contextPtr, this.ptr, key.ast)); + } + + sexpr(): string { + return check(Z3.ast_map_to_string(contextPtr, this.ptr)); } } - keys(): AstVector { - return new AstVectorImpl(this.ctx, Z3.ast_map_keys(this.ctx.ptr, this.ptr)); - } + const ctx: Context = { + ptr: contextPtr, + name, - *values(): IterableIterator { - for (const [_, value] of this.entries()) { - yield value; - } - } - get(key: Key): Value { - return this.ctx._toAst(Z3.ast_map_find(this.ctx.ptr, this.ptr, key.ast)) as Value; - } + ///////////// + // Classes // + ///////////// + Solver: SolverImpl, + Model: ModelImpl, + Tactic: TacticImpl, + AstVector: AstVectorImpl as AstVectorCtor, + AstMap: AstMapImpl as AstMapCtor, - set(key: Key, value: Value): void { - Z3.ast_map_insert(this.ctx.ptr, this.ptr, key.ast, value.ast); - } + /////////////// + // Functions // + /////////////// + interrupt, + isModel, + isAst, + isSort, + isFuncDecl, + isApp, + isConst, + isExpr, + isVar, + isAppOf, + isBool, + isTrue, + isFalse, + isAnd, + isOr, + isImplies, + isNot, + isEq, + isDistinct, + isArith, + isArithSort, + isInt, + isIntVal, + isIntSort, + isReal, + isRealVal, + isRealSort, + isBitVecSort, + isBitVec, + isBitVecVal, // TODO fix ordering + isProbe, + isTactic, + isAstVector, + eqIdentity, + getVarIndex, + from, + solve, - delete(key: Key): void { - Z3.ast_map_erase(this.ctx.ptr, this.ptr, key.ast); - } + ///////////// + // Objects // + ///////////// + Sort, + Function, + RecFunc, + Bool, + Int, + Real, + BitVec, - clear(): void { - Z3.ast_map_reset(this.ctx.ptr, this.ptr); - } - - has(key: Key): boolean { - return Z3.ast_map_contains(this.ctx.ptr, this.ptr, key.ast); - } - - sexpr(): string { - return Z3.ast_map_to_string(this.ctx.ptr, this.ptr); - } + //////////////// + // Operations // + //////////////// + If, + Distinct, + Const, + Consts, + FreshConst, + Var, + Implies, + Eq, + Xor, + Not, + And, + Or, + ToReal, + ToInt, + IsInt, + Sqrt, + Cbrt, + BV2Int, + Int2BV, + Concat, + Cond, + }; + cleanup.register(ctx, () => Z3.del_context(contextPtr)); + return ctx; } return { @@ -1881,8 +1963,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel { getParam, setParam, resetParams, - isContext, - Context: ContextImpl as ContextCtor, + Context: createContext, }; } diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index a056ef686..d8bda01a1 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -16,13 +16,13 @@ import { } from '../low-level'; /** @hidden */ -export type AnySort = +export type AnySort = | Sort | BoolSort | ArithSort | BitVecSort; /** @hidden */ -export type AnyExpr = +export type AnyExpr = | Expr | Bool | Arith @@ -31,10 +31,10 @@ export type AnyExpr = | BitVec | BitVecNum; /** @hidden */ -export type AnyAst = AnyExpr | AnySort | FuncDecl; +export type AnyAst = AnyExpr | AnySort | FuncDecl; /** @hidden */ -export type SortToExprMap, Name extends string = any> = S extends BoolSort +export type SortToExprMap, Name extends string = 'main'> = S extends BoolSort ? Bool : S extends ArithSort ? Arith @@ -45,7 +45,7 @@ export type SortToExprMap, Name extends string = any> = : never; /** @hidden */ -export type CoercibleToExprMap, Name extends string = any> = S extends bigint +export type CoercibleToExprMap, Name extends string = 'main'> = S extends bigint ? IntNum : S extends number | CoercibleRational ? RatNum @@ -76,38 +76,20 @@ export type CoercibleToExprMap, Name extends str export type CoercibleRational = { numerator: bigint | number; denominator: bigint | number }; /** @hidden */ -export type CoercibleToExpr = number | bigint | boolean | CoercibleRational | Expr; +export type CoercibleToExpr = number | bigint | boolean | CoercibleRational | Expr; export class Z3Error extends Error {} export class Z3AssertionError extends Z3Error {} -/** - * Returned by {@link Solver.check} when Z3 could find a solution - * @category Global - */ -export const sat = Symbol('Solver found a solution'); -/** - * Returned by {@link Solver.check} when Z3 couldn't find a solution - * @category Global - */ -export const unsat = Symbol("Solver didn't find a solution"); -/** - * Returned by {@link Solver.check} when Z3 couldn't reason about the assumptions - * @category Global - */ -export const unknown = Symbol("Solver couldn't reason about the assumptions"); /** @category Global */ -export type CheckSatResult = typeof sat | typeof unsat | typeof unknown; +export type CheckSatResult = 'sat' | 'unsat' | 'unknown'; /** @hidden */ export interface ContextCtor { - new (name: Name, options?: Record): Context; + (name: Name, options?: Record): Context; } -export interface Context { - /** @hidden */ - readonly __typename: 'Context'; - +export interface Context { /** @hidden */ readonly ptr: Z3_context; /** @@ -190,7 +172,7 @@ export interface Context { /** @category Functions */ isTactic(obj: unknown): obj is Tactic; /** @category Functions */ - isAstVector(obj: unknown): obj is AstVector, Name>; + isAstVector(obj: unknown): obj is AstVector>; /** * Returns whether two Asts are the same thing * @category Functions */ @@ -202,9 +184,13 @@ export interface Context { * @category Functions */ from(primitive: boolean): Bool; /** - * Coerce a number or rational into a Real expression + * Coerce a number to an Int or Real expression (integral numbers become Ints) * @category Functions */ - from(primitive: number | CoercibleRational): RatNum; + from(primitive: number): IntNum | RatNum; + /** + * Coerce a rational into a Real expression + * @category Functions */ + from(primitive: CoercibleRational): RatNum; /** * Coerce a big number into a Integer expression * @category Functions */ @@ -232,7 +218,7 @@ export interface Context { * * @see {@link Solver} * @category Functions */ - solve(...assertions: Bool[]): Promise; + solve(...assertions: Bool[]): Promise | 'unsat' | 'unknown'>; ///////////// // Classes // @@ -250,9 +236,9 @@ export interface Context { */ readonly Model: new () => Model; /** @category Classes */ - readonly AstVector: new = AnyAst>() => AstVector; + readonly AstVector: new = AnyAst>() => AstVector; /** @category Classes */ - readonly AstMap: new () => AstMap; + readonly AstMap: new = AnyAst, Value extends Ast = AnyAst>() => AstMap; /** @category Classes */ readonly Tactic: new (name: string) => Tactic; @@ -309,7 +295,7 @@ export interface Context { /** @category Operations */ And(): Bool; /** @category Operations */ - And(vector: AstVector, Name>): Bool; + And(vector: AstVector>): Bool; /** @category Operations */ And(...args: (Bool | boolean)[]): Bool; /** @category Operations */ @@ -317,7 +303,7 @@ export interface Context { /** @category Operations */ Or(): Bool; /** @category Operations */ - Or(vector: AstVector, Name>): Bool; + Or(vector: AstVector>): Bool; /** @category Operations */ Or(...args: (Bool | boolean)[]): Bool; /** @category Operations */ @@ -368,9 +354,11 @@ export interface Context { Int2BV(a: Arith | bigint | number, bits: Bits): BitVec; /** @category Operations */ Concat(...bitvecs: BitVec[]): BitVec; + /** @category Operations */ + Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic } -export interface Ast { +export interface Ast { /** @hidden */ readonly __typename: 'Ast' | Sort['__typename'] | FuncDecl['__typename'] | Expr['__typename']; @@ -380,7 +368,7 @@ export interface Ast { /** @virtual */ get ast(): Z3_ast; /** @virtual */ - get id(): number; + id(): number; eqIdentity(other: Ast): boolean; neqIdentity(other: Ast): boolean; @@ -392,7 +380,7 @@ export interface Ast { export interface SolverCtor { new (): Solver; } -export interface Solver { +export interface Solver { /** @hidden */ readonly __typename: 'Solver'; @@ -407,10 +395,10 @@ export interface Solver { pop(num?: number): void; numScopes(): number; reset(): void; - add(...exprs: (Bool | AstVector, Name>)[]): void; + add(...exprs: (Bool | AstVector>)[]): void; addAndTrack(expr: Bool, constant: Bool | string): void; - assertions(): AstVector, Name>; - check(...exprs: (Bool | AstVector, Name>)[]): Promise; + assertions(): AstVector>; + check(...exprs: (Bool | AstVector>)[]): Promise; model(): Model; } @@ -418,14 +406,14 @@ export interface Solver { export interface ModelCtor { new (): Model; } -export interface Model extends Iterable> { +export interface Model extends Iterable> { /** @hidden */ readonly __typename: 'Model'; readonly ctx: Context; readonly ptr: Z3_model; - get length(): number; + length(): number; entries(): IterableIterator<[number, FuncDecl]>; keys(): IterableIterator; @@ -436,10 +424,10 @@ export interface Model extends Iterable, modelCompletion?: boolean): Arith; eval(expr: Expr, modelCompletion?: boolean): Expr; get(i: number): FuncDecl; - get(from: number, to: number): FuncDecl[]; + get(from: number, to: number): FuncDecl[]; get(declaration: FuncDecl): FuncInterp | Expr; get(constant: Expr): Expr; - get(sort: Sort): AstVector, Name>; + get(sort: Sort): AstVector>; } /** @@ -461,7 +449,7 @@ export interface Model extends Iterable { declare(name: string): Sort; } -export interface Sort extends Ast { +export interface Sort extends Ast { /** @hidden */ readonly __typename: 'Sort' | BoolSort['__typename'] | ArithSort['__typename'] | BitVecSort['__typename']; @@ -476,7 +464,7 @@ export interface Sort extends Ast { /** * @category Functions */ -export interface FuncInterp { +export interface FuncInterp { /** @hidden */ readonly __typename: 'FuncInterp'; @@ -516,7 +504,7 @@ export interface RecFuncCreation { /** * @category Functions */ -export interface FuncDecl extends Ast { +export interface FuncDecl extends Ast { /** @hidden */ readonly __typename: 'FuncDecl'; @@ -529,7 +517,7 @@ export interface FuncDecl extends Ast[]): AnyExpr; } -export interface Expr = AnySort, Ptr = unknown> +export interface Expr = AnySort, Ptr = unknown> extends Ast { /** @hidden */ readonly __typename: 'Expr' | Bool['__typename'] | Arith['__typename'] | BitVec['__typename']; @@ -546,7 +534,7 @@ export interface Expr = AnySort< } /** @category Booleans */ -export interface BoolSort extends Sort { +export interface BoolSort extends Sort { /** @hidden */ readonly __typename: 'BoolSort'; @@ -554,7 +542,7 @@ export interface BoolSort extends Sort { cast(expr: CoercibleToExpr): never; } /** @category Booleans */ -export interface BoolCreation { +export interface BoolCreation { sort(): BoolSort; const(name: string): Bool; @@ -565,7 +553,7 @@ export interface BoolCreation { val(value: boolean): Bool; } /** @category Booleans */ -export interface Bool extends Expr, Z3_ast> { +export interface Bool extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'Bool'; @@ -579,7 +567,7 @@ export interface Bool extends Expr extends Sort { +export interface ArithSort extends Sort { /** @hidden */ readonly __typename: 'ArithSort'; @@ -615,7 +603,7 @@ export interface RealCreation { * Represents Integer or Real number expression * @category Arithmetic */ -export interface Arith extends Expr, Z3_ast> { +export interface Arith extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'Arith' | IntNum['__typename'] | RatNum['__typename']; @@ -683,11 +671,11 @@ export interface Arith extends Expr extends Arith { +export interface IntNum extends Arith { /** @hidden */ readonly __typename: 'IntNum'; - get value(): bigint; + value(): bigint; asString(): string; asBinary(): string; } @@ -707,11 +695,11 @@ export interface IntNum extends Arith { * ``` * @category Arithmetic */ -export interface RatNum extends Arith { +export interface RatNum extends Arith { /** @hidden */ readonly __typename: 'RatNum'; - get value(): { numerator: bigint; denominator: bigint }; + value(): { numerator: bigint; denominator: bigint }; numerator(): IntNum; denominator(): IntNum; asNumber(): number; @@ -725,7 +713,7 @@ export interface RatNum extends Arith { * @typeParam Bits - A number representing amount of bits for this sort * @category Bit Vectors */ -export interface BitVecSort extends Sort { +export interface BitVecSort extends Sort { /** @hidden */ readonly __typename: 'BitVecSort'; @@ -739,14 +727,14 @@ export interface BitVecSort): BitVec; cast(other: CoercibleToExpr): Expr; } /** @hidden */ -export type CoercibleToBitVec = +export type CoercibleToBitVec = | bigint | number | BitVec; @@ -769,7 +757,7 @@ export interface BitVecCreation { * Represents Bit Vector expression * @category Bit Vectors */ -export interface BitVec +export interface BitVec extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'BitVec' | BitVecNum['__typename']; @@ -790,7 +778,7 @@ export interface BitVec * // 8 * ``` */ - get size(): Bits; + size(): Bits; /** @category Arithmetic */ add(other: CoercibleToBitVec): BitVec; @@ -959,17 +947,17 @@ export interface BitVec * Represents Bit Vector constant value * @category Bit Vectors */ -export interface BitVecNum extends BitVec { +export interface BitVecNum extends BitVec { /** @hidden */ readonly __typename: 'BitVecNum'; - get value(): bigint; + value(): bigint; asSignedValue(): bigint; asString(): string; asBinaryString(): string; } -export interface Probe { +export interface Probe { /** @hidden */ readonly __typename: 'Probe'; @@ -981,7 +969,7 @@ export interface Probe { export interface TacticCtor { new (name: string): Tactic; } -export interface Tactic { +export interface Tactic { /** @hidden */ readonly __typename: 'Tactic'; @@ -991,7 +979,7 @@ export interface Tactic { /** @hidden */ export interface AstVectorCtor { - new = AnyAst>(): AstVector; + new = AnyAst>(): AstVector; } /** * Stores multiple {@link Ast} objects @@ -1009,13 +997,13 @@ export interface AstVectorCtor { * // [2, x] * ``` */ -export interface AstVector = AnyAst, Name extends string = any> extends Iterable { +export interface AstVector = AnyAst> extends Iterable { /** @hidden */ readonly __typename: 'AstVector'; readonly ctx: Context; readonly ptr: Z3_ast_vector; - get length(): number; + length(): number; entries(): IterableIterator<[number, Item]>; keys(): IterableIterator; @@ -1031,7 +1019,7 @@ export interface AstVector = AnyAst, Name extends string /** @hidden */ export interface AstMapCtor { - new (): AstMap; + new = AnyAst, Value extends Ast = AnyAst>(): AstMap; } /** * Stores a mapping between different {@link Ast} objects @@ -1054,7 +1042,7 @@ export interface AstMapCtor { * // 0 * ``` */ -export interface AstMap = AnyAst, Value extends Ast = AnyAst, Name extends string = any> +export interface AstMap = AnyAst, Value extends Ast = AnyAst> extends Iterable<[Key, Value]> { /** @hidden */ readonly __typename: 'AstMap'; @@ -1064,7 +1052,7 @@ export interface AstMap = AnyAst, Value extends Ast = AnyA get size(): number; entries(): IterableIterator<[Key, Value]>; - keys(): AstVector; + keys(): AstVector; values(): IterableIterator; get(key: Key): Value | undefined; set(key: Key, value: Value): void; @@ -1119,11 +1107,6 @@ export interface Z3HighLevel { */ getParam(name: string): string | null; - /** - * Returns whether the given object is a {@link Context} - */ - isContext(obj: unknown): obj is Context; - /** * Use this to create new contexts * @see {@link Context} diff --git a/src/api/js/src/high-level/utils.test.ts b/src/api/js/src/high-level/utils.test.ts index 945f7e9d0..791782783 100644 --- a/src/api/js/src/high-level/utils.test.ts +++ b/src/api/js/src/high-level/utils.test.ts @@ -1,5 +1,5 @@ import { Z3AssertionError } from './types'; -import { allSatisfy, assert, assertExhaustive, autoBind } from './utils'; +import { allSatisfy, assert, assertExhaustive } from './utils'; describe('allSatisfy', () => { it('returns null on empty array', () => { @@ -56,28 +56,6 @@ describe('assertExhaustive', () => { }); }); -describe('autoBind', () => { - class Binded { - readonly name = 'Richard'; - constructor(shouldBind: boolean) { - if (shouldBind === true) { - autoBind(this); - } - } - - test(): string { - return `Hello ${this.name}`; - } - } - - it('binds this', () => { - const { test: withoutBind } = new Binded(false); - const { test: withBind } = new Binded(true); - expect(() => withoutBind()).toThrowError(TypeError); - expect(withBind()).toStrictEqual('Hello Richard'); - }); -}); - describe('assert', () => { it('throws on failure', () => { expect(() => assert(false)).toThrowError(Z3AssertionError); diff --git a/src/api/js/src/high-level/utils.ts b/src/api/js/src/high-level/utils.ts index bd5013710..2f01da170 100644 --- a/src/api/js/src/high-level/utils.ts +++ b/src/api/js/src/high-level/utils.ts @@ -10,32 +10,6 @@ function getAllProperties(obj: Record) { return properties; } -// https://github.com/sindresorhus/auto-bind -// We modify it to use CommonJS instead of ESM -/* -MIT License - -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -export function autoBind>(self: Self): Self { - for (const [obj, key] of getAllProperties(self.constructor.prototype)) { - if (key === 'constructor') { - continue; - } - const descriptor = Reflect.getOwnPropertyDescriptor(obj, key); - if (descriptor && typeof descriptor.value === 'function') { - (self[key] as any) = self[key].bind(self); - } - } - return self; -} - /** * Use to ensure that switches are checked to be exhaustive at compile time * diff --git a/src/api/js/src/jest.ts b/src/api/js/src/jest.ts index 9cbab31f1..2de6cdab1 100644 --- a/src/api/js/src/jest.ts +++ b/src/api/js/src/jest.ts @@ -3,7 +3,7 @@ // @ts-ignore no-implicit-any import { createApi, Z3HighLevel } from './high-level'; import { init as initWrapper, Z3LowLevel } from './low-level'; -import initModule = require('../build/z3-built'); +import initModule = require('../z3-built'); export * from './high-level/types'; export { Z3Core, Z3LowLevel } from './low-level'; 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/z3/z3.py b/src/api/python/z3/z3.py index 7dbef9f6e..5d728e594 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -9192,6 +9192,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. 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/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 5b0df8147..c69534b08 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -78,7 +78,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { void updt_local_params(params_ref const & _p) { rewriter_params p(_p); - m_flat = p.flat(); + m_flat = true; m_max_memory = megabytes_to_bytes(p.max_memory()); m_max_steps = p.max_steps(); m_pull_cheap_ite = p.pull_cheap_ite(); 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/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 0dbf3c6f9..b8946ebf6 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1291,6 +1291,88 @@ namespace dd { return *this; } + + /** + * \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 d061bb0f7..9cd454698 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -364,9 +364,20 @@ namespace dd { bool different_leading_term(pdd const& other) const { return m.different_leading_term(*this, other); } void factor(unsigned v, unsigned degree, pdd& lc, pdd& rest) { m.factor(*this, v, degree, lc, rest); } + /** + * \brief factor out variables + */ + std::pair var_factors() const; + pdd subst_val(vector> const& s) const { return m.subst_val(*this, s); } pdd subst_val(unsigned v, rational const& val) const { return m.subst_val(*this, v, 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; } @@ -398,13 +409,17 @@ namespace dd { inline pdd operator-(rational const& r, pdd const& b) { return b.rev_sub(r); } inline pdd operator-(int x, pdd const& b) { return rational(x) - b; } inline pdd operator-(pdd const& b, int x) { return b + (-rational(x)); } - + inline pdd operator-(pdd const& b, rational const& r) { return b + (-r); } + inline pdd& operator&=(pdd & p, pdd const& q) { p = p & q; return p; } inline pdd& operator^=(pdd & p, pdd const& q) { p = p ^ q; return p; } inline pdd& operator*=(pdd & p, pdd const& q) { p = p * q; return p; } inline pdd& operator|=(pdd & p, pdd const& q) { p = p | q; return p; } inline pdd& operator-=(pdd & p, pdd const& q) { p = p - q; return p; } inline pdd& operator+=(pdd & p, pdd const& q) { p = p + q; return p; } + inline pdd& operator+=(pdd & p, rational const& v) { p = p + v; return p; } + inline pdd& operator-=(pdd & p, rational const& v) { p = p - v; return p; } + inline pdd& operator*=(pdd & p, rational const& v) { p = p * v; return p; } std::ostream& operator<<(std::ostream& out, pdd const& b); 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 a0947ea42..39a327891 100644 --- a/src/math/grobner/grobner.cpp +++ b/src/math/grobner/grobner.cpp @@ -116,10 +116,13 @@ void grobner::reset() { } void grobner::display_var(std::ostream & out, expr * var) const { + out << "#" << var->get_id(); +#if 0 if (is_app(var) && to_app(var)->get_num_args() > 0) out << mk_bounded_pp(var, m_manager); else out << mk_pp(var, m_manager); +#endif } void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * vars) const { @@ -129,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()) @@ -162,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]; @@ -170,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) { @@ -525,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; @@ -549,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/CMakeLists.txt b/src/opt/CMakeLists.txt index 9c20b7d2d..21075d88c 100644 --- a/src/opt/CMakeLists.txt +++ b/src/opt/CMakeLists.txt @@ -14,6 +14,7 @@ z3_add_component(opt opt_solver.cpp pb_sls.cpp sortmax.cpp + totalizer.cpp wmax.cpp COMPONENT_DEPENDENCIES sat_solver diff --git a/src/opt/maxcore.cpp b/src/opt/maxcore.cpp index 70bf38d87..20cc028aa 100644 --- a/src/opt/maxcore.cpp +++ b/src/opt/maxcore.cpp @@ -13,6 +13,7 @@ Abstract: - mus-mss: based on dual refinement of bounds. - binary: binary version of maxres - rc2: implementaion of rc2 heuristic using cardinality constraints + - rc2t: implementaion of rc2 heuristic using totalizerx - rc2-binary: hybrid of rc2 and binary maxres. Perform one step of binary maxres. If there are more than 16 soft constraints create a cardinality constraint. @@ -60,6 +61,7 @@ Notes: #include "ast/ast_pp.h" #include "ast/pb_decl_plugin.h" #include "ast/ast_util.h" +#include "ast/ast_smt_pp.h" #include "model/model_smt2_pp.h" #include "solver/solver.h" #include "solver/mus.h" @@ -71,6 +73,8 @@ Notes: #include "opt/opt_cores.h" #include "opt/maxsmt.h" #include "opt/maxcore.h" +#include "opt/totalizer.h" +#include using namespace opt; @@ -131,6 +135,7 @@ private: bool m_enable_lns = false; // enable LNS improvements unsigned m_lns_conflicts = 1000; // number of conflicts used for LNS improvement bool m_enable_core_rotate = false; // enable core rotation + bool m_use_totalizer = true; // use totalizer instead of cardinality encoding std::string m_trace_id; typedef ptr_vector exprs; @@ -169,7 +174,10 @@ public: } } - ~maxcore() override {} + ~maxcore() override { + for (auto& [k,t] : m_totalizers) + dealloc(t); + } bool is_literal(expr* l) { return @@ -780,8 +788,38 @@ public: obj_map m_at_mostk; obj_map m_bounds; rational m_unfold_upper; + obj_map m_totalizers; + + expr* mk_atmost_tot(expr_ref_vector const& es, unsigned bound, rational const& weight) { + pb_util pb(m); + expr_ref am(pb.mk_at_most_k(es, 0), m); + totalizer* t = nullptr; + if (!m_totalizers.find(am, t)) { + m_trail.push_back(am); + t = alloc(totalizer, es); + m_totalizers.insert(am, t); + } + expr* at_least = t->at_least(bound + 1); + am = m.mk_not(at_least); + m_trail.push_back(am); + expr_ref_vector& clauses = t->clauses(); + for (auto & clause : clauses) { + add(clause); + m_defs.push_back(clause); + } + clauses.reset(); + auto& defs = t->defs(); + for (auto & [v, d] : defs) + update_model(v, d); + defs.reset(); + bound_info b(es, bound, weight); + m_bounds.insert(am, b); + return am; + } expr* mk_atmost(expr_ref_vector const& es, unsigned bound, rational const& weight) { + if (m_use_totalizer) + return mk_atmost_tot(es, bound, weight); pb_util pb(m); expr_ref am(pb.mk_at_most_k(es, bound), m); expr* r = nullptr; @@ -1024,6 +1062,7 @@ public: m_enable_lns = p.enable_lns(); m_enable_core_rotate = p.enable_core_rotate(); m_lns_conflicts = p.lns_conflicts(); + m_use_totalizer = p.rc2_totalizer(); if (m_c.num_objectives() > 1) m_add_upper_bound_block = false; } @@ -1040,6 +1079,9 @@ public: m_unfold_upper = 0; m_at_mostk.reset(); m_bounds.reset(); + for (auto& [k,t] : m_totalizers) + dealloc(t); + m_totalizers.reset(); return l_true; } @@ -1109,6 +1151,7 @@ opt::maxsmt_solver_base* opt::mk_rc2( return alloc(maxcore, c, id, soft, maxcore::s_rc2); } + opt::maxsmt_solver_base* opt::mk_rc2bin( maxsat_context& c, unsigned id, vector& soft) { return alloc(maxcore, c, id, soft, maxcore::s_primal_binary_rc2); diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index fcc8cdd46..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(); @@ -682,14 +681,11 @@ namespace opt { void context::init_solver() { setup_arith_solver(); + m_sat_solver = nullptr; m_opt_solver = alloc(opt_solver, m, m_params, *m_fm); m_opt_solver->set_logic(m_logic); m_solver = m_opt_solver.get(); - m_opt_solver->ensure_pb(); - - //if (opt_params(m_params).priority() == symbol("pareto") || - // (opt_params(m_params).priority() == symbol("lex") && m_objectives.size() > 1)) { - //} + m_opt_solver->ensure_pb(); } void context::setup_arith_solver() { @@ -708,6 +704,8 @@ namespace opt { if (m_maxsat_engine != symbol("maxres") && m_maxsat_engine != symbol("rc2") && + m_maxsat_engine != symbol("rc2tot") && + m_maxsat_engine != symbol("rc2bin") && m_maxsat_engine != symbol("maxres-bin") && m_maxsat_engine != symbol("maxres-bin-delay") && m_maxsat_engine != symbol("pd-maxres") && @@ -839,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; @@ -862,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); @@ -1569,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 93ecddbe4..893b4bfd6 100644 --- a/src/opt/opt_params.pyg +++ b/src/opt/opt_params.pyg @@ -15,10 +15,12 @@ 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'), ('maxlex.enable', BOOL, True, 'enable maxlex heuristic for lexicographic MaxSAT problems'), + ('rc2.totalizer', BOOL, True, 'use totalizer for rc2 encoding'), ('maxres.hill_climb', BOOL, True, 'give preference for large weight cores'), ('maxres.add_upper_bound_block', BOOL, False, 'restict upper bound with constraint'), ('maxres.max_num_cores', UINT, 200, 'maximal number of cores per round'), diff --git a/src/opt/totalizer.cpp b/src/opt/totalizer.cpp new file mode 100644 index 000000000..41d730560 --- /dev/null +++ b/src/opt/totalizer.cpp @@ -0,0 +1,119 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + totalizer.cpp + +Abstract: + + Incremental totalizer for at least constraints + +Author: + + Nikolaj Bjorner (nbjorner) 2022-06-27 + +--*/ + +#include "opt/totalizer.h" +#include "ast/ast_util.h" +#include "ast/ast_pp.h" +#include + +namespace opt { + + + void totalizer::ensure_bound(node* n, unsigned k) { + auto& lits = n->m_literals; + if (k > lits.size()) + return; + auto* l = n->m_left; + auto* r = n->m_right; + if (l) + ensure_bound(l, k); + if (r) + ensure_bound(r, k); + + expr_ref c(m), def(m); + expr_ref_vector ors(m), clause(m); + for (unsigned i = k; i > 0 && !lits.get(i - 1); --i) { + if (l->size() + r->size() < i) { + lits[i - 1] = m.mk_false(); + continue; + } + + c = m.mk_fresh_const("c", m.mk_bool_sort()); + lits[i - 1] = c; + + // >= 3 + // r[2] => >= 3 + // l[0] & r[1] => >= 3 + // l[1] & r[0] => >= 3 + // l[2] => >= 3 + + ors.reset(); + + for (unsigned j1 = 0; j1 <= i; ++j1) { + unsigned j2 = i - j1; + if (j1 > l->size()) + continue; + if (j2 > r->size()) + continue; + clause.reset(); + if (0 < j1) { + expr* a = l->m_literals.get(j1 - 1); + clause.push_back(mk_not(m, a)); + } + if (0 < j2) { + expr* b = r->m_literals.get(j2 - 1); + clause.push_back(mk_not(m, b)); + } + if (clause.empty()) + continue; + ors.push_back(mk_or(clause)); + clause.push_back(c); + m_clauses.push_back(mk_or(clause)); + } + def = mk_not(m, mk_and(ors)); + m_defs.push_back(std::make_pair(c, def)); + } + } + + totalizer::totalizer(expr_ref_vector const& literals): + m(literals.m()), + m_literals(literals), + m_clauses(m) { + ptr_vector trees; + for (expr* e : literals) { + expr_ref_vector ls(m); + ls.push_back(e); + trees.push_back(alloc(node, ls)); + } + for (unsigned i = 0; i + 1 < trees.size(); i += 2) { + node* left = trees[i]; + node* right = trees[i + 1]; + expr_ref_vector ls(m); + ls.resize(left->size() + right->size()); + node* n = alloc(node, ls); + n->m_left = left; + n->m_right = right; + trees.push_back(n); + } + m_root = trees.back(); + } + + totalizer::~totalizer() { + dealloc(m_root); + } + + expr* totalizer::at_least(unsigned k) { + if (k == 0) + return m.mk_true(); + if (m_root->size() < k) + return m.mk_false(); + SASSERT(1 <= k && k <= m_root->size()); + ensure_bound(m_root, k); + return m_root->m_literals.get(k - 1); + } + +} diff --git a/src/opt/totalizer.h b/src/opt/totalizer.h new file mode 100644 index 000000000..3d26fa2e6 --- /dev/null +++ b/src/opt/totalizer.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + totalizer.h + +Abstract: + + Incremental totalizer for at least constraints + +Author: + + Nikolaj Bjorner (nbjorner) 2022-06-27 + +--*/ + +#pragma once +#include "ast/ast.h" + +namespace opt { + + class totalizer { + struct node { + node* m_left = nullptr; + node* m_right = nullptr; + expr_ref_vector m_literals; + node(expr_ref_vector& l): m_literals(l) {} + ~node() { + dealloc(m_left); + dealloc(m_right); + } + unsigned size() const { return m_literals.size(); } + + }; + + ast_manager& m; + expr_ref_vector m_literals; + node* m_root = nullptr; + expr_ref_vector m_clauses; + vector> m_defs; + + void ensure_bound(node* n, unsigned k); + + public: + totalizer(expr_ref_vector const& literals); + ~totalizer(); + expr* at_least(unsigned k); + expr_ref_vector& clauses() { return m_clauses; } + vector>& defs() { return m_defs; } + }; +} diff --git a/src/params/rewriter_params.pyg b/src/params/rewriter_params.pyg index 18bb29e56..290f7b1da 100644 --- a/src/params/rewriter_params.pyg +++ b/src/params/rewriter_params.pyg @@ -3,7 +3,6 @@ def_module_params('rewriter', export=True, params=(max_memory_param(), max_steps_param(), - ("flat", BOOL, True, "create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor"), ("push_ite_arith", BOOL, False, "push if-then-else over arithmetic terms."), ("push_ite_bv", BOOL, False, "push if-then-else over bit-vector terms."), ("pull_cheap_ite", BOOL, False, "pull if-then-else terms when cheap."), 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/sat_solver.cpp b/src/sat/sat_solver.cpp index 729b3c1f4..9570da396 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -2029,17 +2029,18 @@ namespace sat { sort_watch_lits(); CASSERT("sat_simplify_bug", check_invariant()); - m_probing(); - CASSERT("sat_missed_prop", check_missed_propagation()); - CASSERT("sat_simplify_bug", check_invariant()); - m_asymm_branch(false); - CASSERT("sat_missed_prop", check_missed_propagation()); CASSERT("sat_simplify_bug", check_invariant()); if (m_ext) { m_ext->clauses_modifed(); m_ext->simplify(); } + + m_probing(); + CASSERT("sat_missed_prop", check_missed_propagation()); + CASSERT("sat_simplify_bug", check_invariant()); + m_asymm_branch(false); + if (m_config.m_lookahead_simplify && !m_ext) { lookahead lh(*this); lh.simplify(true); diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index 7cb6a05b6..517efcfe3 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -128,6 +128,22 @@ namespace arith { } else { + + expr_ref mone(a.mk_int(-1), m); + expr_ref abs_q(m.mk_ite(a.mk_ge(q, zero), q, a.mk_uminus(q)), m); + 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(a.mk_sub(mod, abs_q), 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) + + add_clause(eqz, eq); + add_clause(eqz, mod_ge_0); + add_clause(eqz, mod_lt_q); + +#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)); @@ -139,6 +155,8 @@ namespace arith { // q <= 0 or (p mod q) >= 0 // 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)); @@ -148,6 +166,17 @@ namespace arith { add_clause(q_le_0, mod_ge_0); add_clause(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); add_clause(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); +#endif + + if (a.is_zero(p)) { + add_clause(eqz, mk_literal(m.mk_eq(mod, zero))); + add_clause(eqz, mk_literal(m.mk_eq(div, zero))); + } + else if (!a.is_numeral(q)) { + // (or (= y 0) (<= (* y (div x y)) x)) + add_clause(eqz, mk_literal(a.mk_le(a.mk_mul(q, div), p))); + } + } if (get_config().m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { 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 8496717eb..8fb6ff95d 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -434,6 +434,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 bc435d7eb..2a39fae6a 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -208,6 +208,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 f85872db6..c92efdb7d 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -34,7 +34,7 @@ namespace pb { void solver::set_conflict(constraint& c, literal lit) { m_stats.m_num_conflicts++; - TRACE("ba", display(tout, c, true); ); + TRACE("pb", display(tout, c, true); ); if (!validate_conflict(c)) { IF_VERBOSE(0, display(verbose_stream(), c, true)); UNREACHABLE(); @@ -56,7 +56,7 @@ namespace pb { default: m_stats.m_num_propagations++; m_num_propagations_since_pop++; - //TRACE("ba", tout << "#prop: " << m_stats.m_num_propagations << " - " << c.lit() << " => " << lit << "\n";); + //TRACE("pb", tout << "#prop: " << m_stats.m_num_propagations << " - " << c.lit() << " => " << lit << "\n";); SASSERT(validate_unit_propagation(c, lit)); assign(lit, sat::justification::mk_ext_justification(s().scope_lvl(), c.cindex())); break; @@ -69,7 +69,7 @@ namespace pb { void solver::simplify(constraint& p) { SASSERT(s().at_base_lvl()); if (p.lit() != sat::null_literal && value(p.lit()) == l_false) { - TRACE("ba", tout << "pb: flip sign " << p << "\n";); + TRACE("pb", tout << "pb: flip sign " << p << "\n";); IF_VERBOSE(2, verbose_stream() << "sign is flipped " << p << "\n";); return; } @@ -280,7 +280,7 @@ namespace pb { */ lbool solver::add_assign(pbc& p, literal alit) { BADLOG(display(verbose_stream() << "assign: " << alit << " watch: " << p.num_watch() << " size: " << p.size(), p, true)); - TRACE("ba", display(tout << "assign: " << alit << "\n", p, true);); + TRACE("pb", display(tout << "assign: " << alit << "\n", p, true);); SASSERT(!inconsistent()); unsigned sz = p.size(); unsigned bound = p.k(); @@ -290,6 +290,7 @@ namespace pb { SASSERT(p.lit() == sat::null_literal || value(p.lit()) == l_true); SASSERT(num_watch <= sz); SASSERT(num_watch > 0); + SASSERT(validate_watch(p, sat::null_literal)); unsigned index = 0; m_a_max = 0; m_pb_undef.reset(); @@ -311,8 +312,6 @@ namespace pb { return l_undef; } - SASSERT(validate_watch(p, sat::null_literal)); - // SASSERT(validate_watch(p, sat::null_literal)); SASSERT(index < num_watch); unsigned index1 = index + 1; @@ -349,7 +348,7 @@ namespace pb { SASSERT(validate_watch(p, sat::null_literal)); BADLOG(display(verbose_stream() << "conflict: " << alit << " watch: " << p.num_watch() << " size: " << p.size(), p, true)); SASSERT(bound <= slack); - TRACE("ba", tout << "conflict " << alit << "\n";); + TRACE("pb", tout << "conflict " << alit << "\n";); set_conflict(p, alit); return l_false; } @@ -366,13 +365,14 @@ namespace pb { p.swap(num_watch, index); + // // slack >= bound, but slack - w(l) < bound // l must be true. // if (slack < bound + m_a_max) { BADLOG(verbose_stream() << "slack " << slack << " " << bound << " " << m_a_max << "\n";); - TRACE("ba", tout << p << "\n"; for(auto j : m_pb_undef) tout << j << " "; tout << "\n";); + TRACE("pb", tout << p << "\n"; for(auto j : m_pb_undef) tout << j << " "; tout << "\n";); for (unsigned index1 : m_pb_undef) { if (index1 == num_watch) { index1 = index; @@ -389,7 +389,7 @@ namespace pb { SASSERT(validate_watch(p, alit)); // except that alit is still watched. - TRACE("ba", display(tout << "assign: " << alit << "\n", p, true);); + TRACE("pb", display(tout << "assign: " << alit << "\n", p, true);); BADLOG(verbose_stream() << "unwatch " << alit << " watch: " << p.num_watch() << " size: " << p.size() << " slack: " << p.slack() << " " << inconsistent() << "\n"); @@ -547,7 +547,7 @@ namespace pb { literal l = literal(v, c1 < 0); c1 = std::abs(c1); unsigned c = static_cast(c1); - // TRACE("ba", tout << l << " " << c << "\n";); + // TRACE("pb", tout << l << " " << c << "\n";); m_overflow |= c != c1; return wliteral(c, l); } @@ -596,12 +596,13 @@ namespace pb { s().reset_mark(v); --m_num_marks; } - if (idx == 0 && !_debug_conflict) { + if (idx == 0 && !_debug_conflict && m_num_marks > 0) { _debug_conflict = true; _debug_var2position.reserve(s().num_vars()); for (unsigned i = 0; i < lits.size(); ++i) { _debug_var2position[lits[i].var()] = i; } + IF_VERBOSE(0, verbose_stream() << "num marks: " << m_num_marks << "\n"); IF_VERBOSE(0, active2pb(m_A); uint64_t c = 0; @@ -617,20 +618,19 @@ namespace pb { } } m_num_marks = 0; - resolve_conflict(); + resolve_conflict(); + exit(0); } --idx; } } lbool solver::resolve_conflict() { - if (0 == m_num_propagations_since_pop) { + if (0 == m_num_propagations_since_pop) return l_undef; - } - if (s().m_config.m_pb_resolve == sat::PB_ROUNDING) { + if (s().m_config.m_pb_resolve == sat::PB_ROUNDING) return resolve_conflict_rs(); - } m_overflow = false; reset_coeffs(); @@ -638,7 +638,7 @@ namespace pb { m_bound = 0; literal consequent = s().m_not_l; sat::justification js = s().m_conflict; - TRACE("ba", tout << consequent << " " << js << "\n";); + TRACE("pb", tout << consequent << " " << js << "\n";); bool unique_max; m_conflict_lvl = s().get_max_lvl(consequent, js, unique_max); if (m_conflict_lvl == 0) { @@ -667,7 +667,7 @@ namespace pb { } DEBUG_CODE(TRACE("sat_verbose", display(tout, m_A););); - TRACE("ba", tout << "process consequent: " << consequent << " : "; s().display_justification(tout, js) << "\n";); + TRACE("pb", tout << "process consequent: " << consequent << " : "; s().display_justification(tout, js) << "\n";); SASSERT(offset > 0); DEBUG_CODE(justification2pb(js, consequent, offset, m_B);); @@ -741,7 +741,7 @@ namespace pb { inc_bound(offset); inc_coeff(consequent, offset); get_antecedents(consequent, p, m_lemma); - TRACE("ba", display(tout, p, true); tout << m_lemma << "\n";); + TRACE("pb", display(tout, p, true); tout << m_lemma << "\n";); if (_debug_conflict) { verbose_stream() << consequent << " "; verbose_stream() << "antecedents: " << m_lemma << "\n"; @@ -766,7 +766,7 @@ namespace pb { active2pb(m_C); VERIFY(validate_resolvent()); m_A = m_C; - TRACE("ba", display(tout << "conflict: ", m_A););); + TRACE("pb", display(tout << "conflict: ", m_A););); cut(); @@ -887,7 +887,7 @@ namespace pb { } } ineq.divide(c); - TRACE("ba", display(tout << "var: " << v << " " << c << ": ", ineq, true);); + TRACE("pb", display(tout << "var: " << v << " " << c << ": ", ineq, true);); } void solver::round_to_one(bool_var w) { @@ -905,7 +905,7 @@ namespace pb { SASSERT(validate_lemma()); divide(c); SASSERT(validate_lemma()); - TRACE("ba", active2pb(m_B); display(tout, m_B, true);); + TRACE("pb", active2pb(m_B); display(tout, m_B, true);); } void solver::divide(unsigned c) { @@ -935,14 +935,14 @@ namespace pb { } void solver::resolve_with(ineq const& ineq) { - TRACE("ba", display(tout, ineq, true);); + TRACE("pb", display(tout, ineq, true);); inc_bound(ineq.m_k); - TRACE("ba", tout << "bound: " << m_bound << "\n";); + TRACE("pb", tout << "bound: " << m_bound << "\n";); for (unsigned i = ineq.size(); i-- > 0; ) { literal l = ineq.lit(i); inc_coeff(l, static_cast(ineq.coeff(i))); - TRACE("ba", tout << "bound: " << m_bound << " lit: " << l << " coeff: " << ineq.coeff(i) << "\n";); + TRACE("pb", tout << "bound: " << m_bound << " lit: " << l << " coeff: " << ineq.coeff(i) << "\n";); } } @@ -995,11 +995,11 @@ namespace pb { consequent.neg(); process_antecedent(consequent, 1); } - TRACE("ba", tout << consequent << " " << js << "\n";); + TRACE("pb", tout << consequent << " " << js << "\n";); unsigned idx = s().m_trail.size() - 1; do { - TRACE("ba", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; + TRACE("pb", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; if (consequent != sat::null_literal) { active2pb(m_A); display(tout, m_A, true); } ); @@ -1069,7 +1069,7 @@ namespace pb { } else { SASSERT(k > c); - TRACE("ba", tout << "visited: " << l << "\n";); + TRACE("pb", tout << "visited: " << l << "\n";); k -= c; } } @@ -1118,7 +1118,7 @@ namespace pb { } } if (idx == 0) { - TRACE("ba", tout << "there is no consequent\n";); + TRACE("pb", tout << "there is no consequent\n";); goto bail_out; } --idx; @@ -1131,7 +1131,7 @@ namespace pb { js = s().m_justification[v]; } while (m_num_marks > 0 && !m_overflow); - TRACE("ba", active2pb(m_A); display(tout, m_A, true);); + TRACE("pb", active2pb(m_A); display(tout, m_A, true);); // TBD: check if this is useful if (!m_overflow && consequent != sat::null_literal) { @@ -1143,7 +1143,7 @@ namespace pb { } bail_out: - TRACE("ba", tout << "bail " << m_overflow << "\n";); + TRACE("pb", tout << "bail " << m_overflow << "\n";); if (m_overflow) { ++m_stats.m_num_overflow; m_overflow = false; @@ -1199,23 +1199,23 @@ namespace pb { } } if (slack >= 0) { - TRACE("ba", tout << "slack is non-negative\n";); + TRACE("pb", tout << "slack is non-negative\n";); IF_VERBOSE(20, verbose_stream() << "(sat.card slack: " << slack << " skipped: " << num_skipped << ")\n";); return false; } if (m_overflow) { - TRACE("ba", tout << "overflow\n";); + TRACE("pb", tout << "overflow\n";); return false; } if (m_lemma[0] == sat::null_literal) { if (m_lemma.size() == 1) { s().set_conflict(sat::justification(0)); } - TRACE("ba", tout << "no asserting literal\n";); + TRACE("pb", tout << "no asserting literal\n";); return false; } - TRACE("ba", tout << m_lemma << "\n";); + TRACE("pb", tout << m_lemma << "\n";); if (get_config().m_drat && m_solver) { s().m_drat.add(m_lemma, sat::status::th(true, get_id())); @@ -1224,7 +1224,7 @@ namespace pb { s().m_lemma.reset(); s().m_lemma.append(m_lemma); for (unsigned i = 1; i < m_lemma.size(); ++i) { - CTRACE("ba", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); + CTRACE("pb", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); s().mark(m_lemma[i].var()); } return true; @@ -1346,11 +1346,11 @@ namespace pb { } solver::solver(ast_manager& m, sat::sat_internalizer& si, euf::theory_id id) - : euf::th_solver(m, symbol("ba"), id), + : euf::th_solver(m, symbol("pb"), id), si(si), m_pb(m), m_lookahead(nullptr), m_constraint_id(0), m_ba(*this), m_sort(m_ba) { - TRACE("ba", tout << this << "\n";); + TRACE("pb", tout << this << "\n";); m_num_propagations_since_pop = 0; } @@ -1418,6 +1418,8 @@ namespace pb { } else if (lit == sat::null_literal) { init_watch(*c); + if (c->is_pb()) + validate_watch(c->to_pb(), sat::null_literal); } else { if (m_solver) m_solver->set_external(lit.var()); @@ -1569,7 +1571,7 @@ namespace pb { } void solver::get_antecedents(literal l, pbc const& p, literal_vector& r) { - TRACE("ba", display(tout << l << " level: " << s().scope_lvl() << " ", p, true);); + TRACE("pb", display(tout << l << " level: " << s().scope_lvl() << " ", p, true);); SASSERT(p.lit() == sat::null_literal || value(p.lit()) == l_true); if (p.lit() != sat::null_literal) { @@ -1621,7 +1623,7 @@ namespace pb { if (j < p.num_watch()) { j = p.num_watch(); } - CTRACE("ba", coeff == 0, display(tout << l << " coeff: " << coeff << "\n", p, true);); + CTRACE("pb", coeff == 0, display(tout << l << " coeff: " << coeff << "\n", p, true);); if (_debug_conflict) { IF_VERBOSE(0, verbose_stream() << "coeff " << coeff << "\n";); @@ -1672,7 +1674,7 @@ namespace pb { for (unsigned i = 0; !found && i < c.k(); ++i) { found = c[i] == l; } - CTRACE("ba",!found, s().display(tout << l << ":" << c << "\n");); + CTRACE("pb",!found, s().display(tout << l << ":" << c << "\n");); SASSERT(found);); VERIFY(c.lit() == sat::null_literal || value(c.lit()) != l_false); @@ -1712,7 +1714,7 @@ namespace pb { } void solver::remove_constraint(constraint& c, char const* reason) { - TRACE("ba", display(tout << "remove ", c, true) << " " << reason << "\n";); + TRACE("pb", display(tout << "remove ", c, true) << " " << reason << "\n";); IF_VERBOSE(21, display(verbose_stream() << "remove " << reason << " ", c, true);); c.nullify_tracking_literal(*this); clear_watch(c); @@ -1886,7 +1888,7 @@ namespace pb { } void solver::gc_half(char const* st_name) { - TRACE("ba", tout << "gc\n";); + TRACE("pb", tout << "gc\n";); unsigned sz = m_learned.size(); unsigned new_sz = sz/2; unsigned removed = 0; @@ -1933,7 +1935,7 @@ namespace pb { // literal is assigned to false. unsigned sz = c.size(); unsigned bound = c.k(); - TRACE("ba", tout << "assign: " << c.lit() << ": " << ~alit << "@" << lvl(~alit) << " " << c << "\n";); + TRACE("pb", tout << "assign: " << c.lit() << ": " << ~alit << "@" << lvl(~alit) << " " << c << "\n";); SASSERT(0 < bound && bound <= sz); if (bound == sz) { @@ -1971,7 +1973,7 @@ namespace pb { // conflict if (bound != index && value(c[bound]) == l_false) { - TRACE("ba", tout << "conflict " << c[bound] << " " << alit << "\n";); + TRACE("pb", tout << "conflict " << c[bound] << " " << alit << "\n";); if (c.lit() != sat::null_literal && value(c.lit()) == l_undef) { if (index + 1 < bound) c.swap(index, bound - 1); assign(c, ~c.lit()); @@ -1985,7 +1987,7 @@ namespace pb { c.swap(index, bound); } - // TRACE("ba", tout << "no swap " << index << " " << alit << "\n";); + // TRACE("pb", tout << "no swap " << index << " " << alit << "\n";); // there are no literals to swap with, // prepare for unit propagation by swapping the false literal into // position bound. Then literals in positions 0..bound-1 have to be @@ -2351,7 +2353,7 @@ namespace pb { } if (!all_units) { - TRACE("ba", tout << "replacing by pb: " << c << "\n";); + TRACE("pb", tout << "replacing by pb: " << c << "\n";); m_wlits.reset(); for (unsigned i = 0; i < sz; ++i) { m_wlits.push_back(wliteral(coeffs[i], c[i])); @@ -2914,13 +2916,13 @@ namespace pb { SASSERT(&c1 != &c2); if (subsumes(c1, c2, slit)) { if (slit.empty()) { - TRACE("ba", tout << "subsume cardinality\n" << c1 << "\n" << c2 << "\n";); + TRACE("pb", tout << "subsume cardinality\n" << c1 << "\n" << c2 << "\n";); remove_constraint(c2, "subsumed"); ++m_stats.m_num_pb_subsumes; set_non_learned(c1); } else { - TRACE("ba", tout << "self subsume cardinality\n";); + TRACE("pb", tout << "self subsume cardinality\n";); IF_VERBOSE(11, verbose_stream() << "self-subsume cardinality\n"; verbose_stream() << c1 << "\n"; @@ -2952,7 +2954,7 @@ namespace pb { // self-subsumption is TBD } else { - TRACE("ba", tout << "remove\n" << c1 << "\n" << c2 << "\n";); + TRACE("pb", tout << "remove\n" << c1 << "\n" << c2 << "\n";); removed_clauses.push_back(&c2); ++m_stats.m_num_clause_subsumes; set_non_learned(c1); @@ -3284,7 +3286,7 @@ namespace pb { val += wl.first; } } - CTRACE("ba", val >= 0, active2pb(m_A); display(tout, m_A, true);); + CTRACE("pb", val >= 0, active2pb(m_A); display(tout, m_A, true);); return val < 0; } @@ -3297,7 +3299,7 @@ namespace pb { if (!is_false(wl.second)) k += wl.first; } - CTRACE("ba", k > 0, display(tout, ineq, true);); + CTRACE("pb", k > 0, display(tout, ineq, true);); return k <= 0; } @@ -3356,7 +3358,7 @@ namespace pb { return nullptr; } constraint* c = add_pb_ge(sat::null_literal, m_wlits, m_bound, true); - TRACE("ba", if (c) display(tout, *c, true);); + TRACE("pb", if (c) display(tout, *c, true);); ++m_stats.m_num_lemmas; return c; } @@ -3587,7 +3589,7 @@ namespace pb { s0.assign_scoped(l2); s0.assign_scoped(l3); lbool is_sat = s0.check(); - TRACE("ba", s0.display(tout << "trying sat encoding");); + TRACE("pb", s0.display(tout << "trying sat encoding");); if (is_sat == l_false) return true; IF_VERBOSE(0, @@ -3698,11 +3700,11 @@ namespace pb { bool solver::validate_conflict(literal_vector const& lits, ineq& p) { for (literal l : lits) { if (value(l) != l_false) { - TRACE("ba", tout << "literal " << l << " is not false\n";); + TRACE("pb", tout << "literal " << l << " is not false\n";); return false; } if (!p.contains(l)) { - TRACE("ba", tout << "lemma contains literal " << l << " not in inequality\n";); + TRACE("pb", tout << "lemma contains literal " << l << " not in inequality\n";); return false; } } @@ -3713,7 +3715,7 @@ namespace pb { value += coeff; } } - CTRACE("ba", value >= p.m_k, tout << "slack: " << value << " bound " << p.m_k << "\n"; + CTRACE("pb", value >= p.m_k, tout << "slack: " << value << " bound " << p.m_k << "\n"; display(tout, p); tout << lits << "\n";); return value < p.m_k; diff --git a/src/sat/smt/recfun_solver.h b/src/sat/smt/recfun_solver.h index d469b7437..4e41a35a9 100644 --- a/src/sat/smt/recfun_solver.h +++ b/src/sat/smt/recfun_solver.h @@ -108,7 +108,7 @@ namespace recfun { bool is_shared(euf::theory_var v) const override { return true; } void init_search() override {} bool should_research(sat::literal_vector const& core) override; - bool is_beta_redex(euf::enode* p, euf::enode* n) const; + bool is_beta_redex(euf::enode* p, euf::enode* n) const override; void add_assumptions(sat::literal_set& assumptions) override; bool tracking_assumptions() override { return true; } }; 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 33b3e458f..2cf29ffa8 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -71,7 +71,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 88db34012..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,11 +571,13 @@ 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", - tout << "eqz: " << eqz << " neq: " << eq << "\n"; + tout << "eqz: " << eqz << "\n"; + tout << "neq: " << eq << "\n"; tout << "lower: " << lower << "\n"; tout << "upper: " << upper << "\n";); @@ -583,14 +586,20 @@ namespace smt { mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; - if (!m_util.is_numeral(divisor)) { - // (=> (> y 0) (<= (* y (div x y)) x)) - // (=> (< y 0) ???) - expr_ref div_ge(m), div_non_pos(m); + 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)); + } + + // (or (= y 0) (<= (* y (div x y)) x)) + else if (!m_util.is_numeral(divisor)) { + 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); - div_non_pos = m_util.mk_le(divisor, zero); - mk_axiom(div_non_pos, div_ge, false); + s(div_ge); + mk_axiom(eqz, div_ge, false); + TRACE("arith", tout << eqz << " " << div_ge << "\n"); } if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && diff --git a/src/smt/theory_arith_nl.h b/src/smt/theory_arith_nl.h index 044a42e7e..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); } @@ -2154,13 +2148,15 @@ void theory_arith::set_gb_exhausted() { // Scan the grobner basis eqs, and look for inconsistencies. template bool theory_arith::get_gb_eqs_and_look_for_conflict(ptr_vector& eqs, grobner& gb) { - TRACE("grobner", ); eqs.reset(); gb.get_equations(eqs); - TRACE("grobner_bug", tout << "after gb\n";); + TRACE("grobner", tout << "after gb\n"; + std::function _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); }; + for (grobner::equation* eq : eqs) + gb.display_equation(tout, *eq, _fn); + ); for (grobner::equation* eq : eqs) { - TRACE("grobner_bug", gb.display_equation(tout, *eq);); if (is_inconsistent(eq, gb) || is_inconsistent2(eq, gb)) { TRACE("grobner", tout << "inconsistent: "; gb.display_equation(tout, *eq);); return true; @@ -2258,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_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 e1285e749..00b607fbc 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; @@ -3216,18 +3215,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/smt/theory_special_relations.cpp b/src/smt/theory_special_relations.cpp index b25c2bac8..40f94cdbc 100644 --- a/src/smt/theory_special_relations.cpp +++ b/src/smt/theory_special_relations.cpp @@ -36,7 +36,7 @@ namespace smt { if (!m_next) { sort* s = decl()->get_domain(0); sort* domain[2] = {s, s}; - m_next = m.mk_fresh_func_decl("next", "", 2, domain, s); + m_next = m.mk_fresh_func_decl("specrel.next", "", 2, domain, s, false); } return m_next; } diff --git a/src/smt/theory_user_propagator.cpp b/src/smt/theory_user_propagator.cpp index 780023fab..911d1715e 100644 --- a/src/smt/theory_user_propagator.cpp +++ b/src/smt/theory_user_propagator.cpp @@ -20,6 +20,7 @@ Author: #include "smt/theory_bv.h" #include "smt/theory_user_propagator.h" #include "smt/smt_context.h" +#include "ast/ast_ll_pp.h" using namespace smt; @@ -49,6 +50,7 @@ void theory_user_propagator::add_expr(expr* term, bool ensure_enode) { expr_ref r(m); expr* e = term; ctx.get_rewriter()(e, r); + TRACE("user_propagate", tout << "add " << mk_bounded_pp(e, m) << "\n"); if (r != e) { r = m.mk_fresh_const("aux-expr", e->get_sort()); expr_ref eq(m.mk_eq(r, e), m); diff --git a/src/solver/combined_solver.cpp b/src/solver/combined_solver.cpp index bfe495b6e..29a5c0a8f 100644 --- a/src/solver/combined_solver.cpp +++ b/src/solver/combined_solver.cpp @@ -343,7 +343,49 @@ public: else return m_solver2->get_labels(r); } + + void user_propagate_init( + void* ctx, + user_propagator::push_eh_t& push_eh, + user_propagator::pop_eh_t& pop_eh, + user_propagator::fresh_eh_t& fresh_eh) override { + switch_inc_mode(); + m_solver2->user_propagate_init(ctx, push_eh, pop_eh, fresh_eh); + } + + void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { + m_solver2->user_propagate_register_fixed(fixed_eh); + } + + void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { + m_solver2->user_propagate_register_final(final_eh); + } + + void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { + m_solver2->user_propagate_register_eq(eq_eh); + } + + void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { + m_solver2->user_propagate_register_diseq(diseq_eh); + } + + void user_propagate_register_expr(expr* e) override { + m_solver2->user_propagate_register_expr(e); + } + + void user_propagate_register_created(user_propagator::created_eh_t& r) override { + m_solver2->user_propagate_register_created(r); + } + + void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { + m_solver2->user_propagate_register_decide(r); + } + + void user_propagate_clear() override { + m_solver2->user_propagate_clear(); + } + }; 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/CMakeLists.txt b/src/test/CMakeLists.txt index 500cb4258..f959e9bd5 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -120,6 +120,7 @@ add_executable(test-z3 theory_pb.cpp timeout.cpp total_order.cpp + totalizer.cpp trigo.cpp udoc_relation.cpp uint_set.cpp diff --git a/src/test/main.cpp b/src/test/main.cpp index 6272c2dee..f9e4e0815 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -263,4 +263,5 @@ int main(int argc, char ** argv) { TST(solver_pool); //TST_ARGV(hs); TST(finder); + TST(totalizer); } diff --git a/src/test/pdd.cpp b/src/test/pdd.cpp index b0cbb657c..9139e499f 100644 --- a/src/test/pdd.cpp +++ b/src/test/pdd.cpp @@ -323,6 +323,62 @@ public : SASSERT(!(2*a*b + 3*b + 2).is_non_zero()); } + 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); + } + } + } + } + }; } @@ -337,4 +393,5 @@ void tst_pdd() { dd::test::order(); dd::test::order_lm(); dd::test::mod4_operations(); + dd::test::factors(); } diff --git a/src/test/totalizer.cpp b/src/test/totalizer.cpp new file mode 100644 index 000000000..20d8edb70 --- /dev/null +++ b/src/test/totalizer.cpp @@ -0,0 +1,22 @@ +#include "opt/totalizer.h" +#include "ast/ast_pp.h" +#include "ast/reg_decl_plugins.h" +#include + +void tst_totalizer() { + std::cout << "totalizer\n"; + ast_manager m; + reg_decl_plugins(m); + expr_ref_vector lits(m); + for (unsigned i = 0; i < 5; ++i) + lits.push_back(m.mk_fresh_const("a", m.mk_bool_sort())); + opt::totalizer tot(lits); + + for (unsigned i = 0; i <= 6; ++i) { + std::cout << "at least " << i << " "; + expr* am = tot.at_least(i); + std::cout << mk_pp(am, m) << "\n"; + } + for (auto& clause : tot.clauses()) + std::cout << clause << "\n"; +} diff --git a/src/util/mpz.cpp b/src/util/mpz.cpp index bdad1ddfe..736b401b8 100644 --- a/src/util/mpz.cpp +++ b/src/util/mpz.cpp @@ -1842,7 +1842,7 @@ std::string mpz_manager::to_string(mpz const & a) const { template unsigned mpz_manager::hash(mpz const & a) { if (is_small(a)) - return a.m_val; + return ::abs(a.m_val); #ifndef _MP_GMP unsigned sz = size(a); if (sz == 1) diff --git a/src/util/parray.h b/src/util/parray.h index 0c3173f6c..f8f2d7e54 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,7 +310,7 @@ public: } void check_size(cell* c) const { - unsigned r; + [[maybe_unused]] unsigned r; while (c) { switch (c->kind()) { case SET: @@ -333,7 +333,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 +451,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 +536,7 @@ public: r.m_updt_counter = 0; SASSERT(r.root()); } - + void reroot(ref & r) { if (r.root()) return; @@ -545,7 +545,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 +556,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 +574,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; diff --git a/src/util/rational.h b/src/util/rational.h index dffcc52f3..f28a502ef 100644 --- a/src/util/rational.h +++ b/src/util/rational.h @@ -343,7 +343,7 @@ public: static rational power_of_two(unsigned k); - bool is_power_of_two(unsigned & shift) { + bool is_power_of_two(unsigned & shift) const { return m().is_power_of_two(m_val, shift); }