diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index eec9032df..019eb18b1 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -27,7 +27,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.android-abi }} "-DCMAKE_ANDROID_NDK=$ANDROID_NDK_LATEST_HOME" -DZ3_BUILD_JAVA_BINDINGS=TRUE -G "Unix Makefiles" -DJAVA_AWT_LIBRARY=NotNeeded -DJAVA_JVM_LIBRARY=NotNeeded -DJAVA_INCLUDE_PATH2=NotNeeded -DJAVA_AWT_INCLUDE_PATH=NotNeeded ../ + cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_API=21 -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.android-abi }} "-DCMAKE_ANDROID_NDK=$ANDROID_NDK_LATEST_HOME" -DZ3_BUILD_JAVA_BINDINGS=TRUE -G "Unix Makefiles" ../ make -j $(nproc) tar -cvf z3-build-${{ matrix.android-abi }}.tar *.jar *.so diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 04d13b1e8..8a669b2d0 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -41,7 +41,7 @@ jobs: type=edge type=sha,prefix=ubuntu-20.04-bare-z3-sha- - name: Build and push Bare Z3 Docker Image - uses: docker/build-push-action@v3.1.0 + uses: docker/build-push-action@v3.1.1 with: context: . push: true diff --git a/.github/workflows/msvc-static-build.yml b/.github/workflows/msvc-static-build.yml new file mode 100644 index 000000000..81fb9c45a --- /dev/null +++ b/.github/workflows/msvc-static-build.yml @@ -0,0 +1,20 @@ +name: MSVC Static Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: windows-2019 + env: + BUILD_TYPE: Release + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Build + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DZ3_BUILD_LIBZ3_SHARED=OFF -DZ3_BUILD_LIBZ3_MSVC_STATIC=ON + cmake --build build --config ${{ env.BUILD_TYPE }} --parallel + diff --git a/.github/workflows/wasm-release.yml b/.github/workflows/wasm-release.yml index 255de7dc5..c34571784 100644 --- a/.github/workflows/wasm-release.yml +++ b/.github/workflows/wasm-release.yml @@ -12,6 +12,9 @@ defaults: env: EM_VERSION: 3.1.15 +permissions: + contents: read # to fetch code (actions/checkout) + jobs: publish: name: Publish diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index bd76c8033..418438635 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -12,6 +12,9 @@ defaults: env: EM_VERSION: 3.1.15 +permissions: + contents: read # to fetch code (actions/checkout) + jobs: check: name: Check diff --git a/.gitignore b/.gitignore index 3fe3a3110..ffc50c1ba 100644 --- a/.gitignore +++ b/.gitignore @@ -91,4 +91,5 @@ examples/**/obj CMakeSettings.json # Editor temp files *.swp -.DS_Store \ No newline at end of file +.DS_Store +dbg/** diff --git a/CMakeLists.txt b/CMakeLists.txt index 606730c73..21914c587 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.11.0.0 LANGUAGES CXX C) +project(Z3 VERSION 4.11.3.0 LANGUAGES CXX C) ################################################################################ # Project version @@ -304,6 +304,7 @@ endif() # Option to control what type of library we build ################################################################################ option(Z3_BUILD_LIBZ3_SHARED "Build libz3 as a shared library if true, otherwise build a static library" ON) +option(Z3_BUILD_LIBZ3_MSVC_STATIC "Build libz3 as a statically-linked runtime library" OFF) ################################################################################ diff --git a/Parameters.md b/Parameters.md new file mode 100644 index 000000000..ea69becad --- /dev/null +++ b/Parameters.md @@ -0,0 +1,605 @@ +## Module pi + +Description: pattern inference (heuristics) for universal formulas (without annotation) + Parameter | Type | Description | Default + ----------|------|-------------|-------- +arith | unsigned int | 0 - do not infer patterns with arithmetic terms, 1 - use patterns with arithmetic terms if there is no other pattern, 2 - always use patterns with arithmetic terms | 1 +arith_weight | unsigned int | default weight for quantifiers where the only available pattern has nested arithmetic terms | 5 +block_loop_patterns | bool | block looping patterns during pattern inference | true +max_multi_patterns | unsigned int | when patterns are not provided, the prover uses a heuristic to infer them, this option sets the threshold on the number of extra multi-patterns that can be created; by default, the prover creates at most one multi-pattern when there is no unary pattern | 0 +non_nested_arith_weight | unsigned int | default weight for quantifiers where the only available pattern has non nested arithmetic terms | 10 +pull_quantifiers | bool | pull nested quantifiers, if no pattern was found | true +use_database | bool | use pattern database | false +warnings | bool | enable/disable warning messages in the pattern inference module | false + +## Module tactic + +Description: tactic parameters + Parameter | Type | Description | Default + ----------|------|-------------|-------- +blast_term_ite.max_inflation | unsigned int | multiplicative factor of initial term size. | 4294967295 +blast_term_ite.max_steps | unsigned int | maximal number of steps allowed for tactic. | 4294967295 +default_tactic | symbol | overwrite default tactic in strategic solver | +propagate_values.max_rounds | unsigned int | maximal number of rounds to propagate values. | 4 +solve_eqs.context_solve | bool | solve equalities within disjunctions. | true +solve_eqs.ite_solver | bool | use if-then-else solvers. | true +solve_eqs.max_occs | unsigned int | maximum number of occurrences for considering a variable for gaussian eliminations. | 4294967295 +solve_eqs.theory_solver | bool | use theory solvers. | true + +## Module pp + +Description: pretty printer + Parameter | Type | Description | Default + ----------|------|-------------|-------- +bounded | bool | ignore characters exceeding max width | false +bv_literals | bool | use Bit-Vector literals (e.g, #x0F and #b0101) during pretty printing | true +bv_neg | bool | use bvneg when displaying Bit-Vector literals where the most significant bit is 1 | false +decimal | bool | pretty print real numbers using decimal notation (the output may be truncated). Z3 adds a ? if the value is not precise | false +decimal_precision | unsigned int | maximum number of decimal places to be used when pp.decimal=true | 10 +fixed_indent | bool | use a fixed indentation for applications | false +flat_assoc | bool | flat associative operators (when pretty printing SMT2 terms/formulas) | true +fp_real_literals | bool | use real-numbered floating point literals (e.g, +1.0p-1) during pretty printing | false +max_depth | unsigned int | max. term depth (when pretty printing SMT2 terms/formulas) | 5 +max_indent | unsigned int | max. indentation in pretty printer | 4294967295 +max_num_lines | unsigned int | max. number of lines to be displayed in pretty printer | 4294967295 +max_ribbon | unsigned int | max. ribbon (width - indentation) in pretty printer | 80 +max_width | unsigned int | max. width in pretty printer | 80 +min_alias_size | unsigned int | min. size for creating an alias for a shared term (when pretty printing SMT2 terms/formulas) | 10 +pretty_proof | bool | use slower, but prettier, printer for proofs | false +simplify_implies | bool | simplify nested implications for pretty printing | true +single_line | bool | ignore line breaks when true | false + +## Module sat + +Description: propositional SAT solver + Parameter | Type | Description | Default + ----------|------|-------------|-------- +abce | bool | eliminate blocked clauses using asymmetric literals | false +acce | bool | eliminate covered clauses using asymmetric added literals | false +anf | bool | enable ANF based simplification in-processing | false +anf.delay | unsigned int | delay ANF simplification by in-processing round | 2 +anf.exlin | bool | enable extended linear simplification | false +asymm_branch | bool | asymmetric branching | true +asymm_branch.all | bool | asymmetric branching on all literals per clause | false +asymm_branch.delay | unsigned int | number of simplification rounds to wait until invoking asymmetric branch simplification | 1 +asymm_branch.limit | unsigned int | approx. maximum number of literals visited during asymmetric branching | 100000000 +asymm_branch.rounds | unsigned int | maximal number of rounds to run asymmetric branch simplifications if progress is made | 2 +asymm_branch.sampled | bool | use sampling based asymmetric branching based on binary implication graph | true +ate | bool | asymmetric tautology elimination | true +backtrack.conflicts | unsigned int | number of conflicts before enabling chronological backtracking | 4000 +backtrack.scopes | unsigned int | number of scopes to enable chronological backtracking | 100 +bca | bool | blocked clause addition - add blocked binary clauses | false +bce | bool | eliminate blocked clauses | false +bce_at | unsigned int | eliminate blocked clauses only once at the given simplification round | 2 +bce_delay | unsigned int | delay eliminate blocked clauses until simplification round | 2 +binspr | bool | enable SPR inferences of binary propagation redundant clauses. This inprocessing step eliminates models | false +blocked_clause_limit | unsigned int | maximum number of literals visited during blocked clause elimination | 100000000 +branching.anti_exploration | bool | apply anti-exploration heuristic for branch selection | false +branching.heuristic | symbol | branching heuristic vsids, chb | vsids +burst_search | unsigned int | number of conflicts before first global simplification | 100 +cardinality.encoding | symbol | encoding used for at-most-k constraints: grouped, bimander, ordered, unate, circuit | grouped +cardinality.solver | bool | use cardinality solver | true +cce | bool | eliminate covered clauses | false +core.minimize | bool | minimize computed core | false +core.minimize_partial | bool | apply partial (cheap) core minimization | false +cut | bool | enable AIG based simplification in-processing | false +cut.aig | bool | extract aigs (and ites) from cluases for cut simplification | false +cut.delay | unsigned int | delay cut simplification by in-processing round | 2 +cut.dont_cares | bool | integrate dont cares with cuts | true +cut.force | bool | force redoing cut-enumeration until a fixed-point | false +cut.lut | bool | extract luts from clauses for cut simplification | false +cut.npn3 | bool | extract 3 input functions from clauses for cut simplification | false +cut.redundancies | bool | integrate redundancy checking of cuts | true +cut.xor | bool | extract xors from clauses for cut simplification | false +ddfw.init_clause_weight | unsigned int | initial clause weight for DDFW local search | 8 +ddfw.reinit_base | unsigned int | increment basis for geometric backoff scheme of re-initialization of weights | 10000 +ddfw.restart_base | unsigned int | number of flips used a starting point for hessitant restart backoff | 100000 +ddfw.threads | unsigned int | number of ddfw threads to run in parallel with sat solver | 0 +ddfw.use_reward_pct | unsigned int | percentage to pick highest reward variable when it has reward 0 | 15 +ddfw_search | bool | use ddfw local search instead of CDCL | false +dimacs.core | bool | extract core from DIMACS benchmarks | false +drat.activity | bool | dump variable activities | false +drat.binary | bool | use Binary DRAT output format | false +drat.check_sat | bool | build up internal trace, check satisfying model | false +drat.check_unsat | bool | build up internal proof and check | false +drat.file | symbol | file to dump DRAT proofs | +drup.trim | bool | build and trim drup proof | false +dyn_sub_res | bool | dynamic subsumption resolution for minimizing learned clauses | true +elim_vars | bool | enable variable elimination using resolution during simplification | true +elim_vars_bdd | bool | enable variable elimination using BDD recompilation during simplification | true +elim_vars_bdd_delay | unsigned int | delay elimination of variables using BDDs until after simplification round | 3 +enable_pre_simplify | bool | enable pre simplifications before the bounded search | false +euf | bool | enable euf solver (this feature is preliminary and not ready for general consumption) | false +force_cleanup | bool | force cleanup to remove tautologies and simplify clauses | false +gc | symbol | garbage collection strategy: psm, glue, glue_psm, dyn_psm | glue_psm +gc.burst | bool | perform eager garbage collection during initialization | false +gc.defrag | bool | defragment clauses when garbage collecting | true +gc.increment | unsigned int | increment to the garbage collection threshold | 500 +gc.initial | unsigned int | learned clauses garbage collection frequency | 20000 +gc.k | unsigned int | learned clauses that are inactive for k gc rounds are permanently deleted (only used in dyn_psm) | 7 +gc.small_lbd | unsigned int | learned clauses with small LBD are never deleted (only used in dyn_psm) | 3 +inprocess.max | unsigned int | maximal number of inprocessing passes | 4294967295 +inprocess.out | symbol | file to dump result of the first inprocessing step and exit | +local_search | bool | use local search instead of CDCL | false +local_search_dbg_flips | bool | write debug information for number of flips | false +local_search_mode | symbol | local search algorithm, either default wsat or qsat | wsat +local_search_threads | unsigned int | number of local search threads to find satisfiable solution | 0 +lookahead.cube.cutoff | symbol | cutoff type used to create lookahead cubes: depth, freevars, psat, adaptive_freevars, adaptive_psat | depth +lookahead.cube.depth | unsigned int | cut-off depth to create cubes. Used when lookahead.cube.cutoff is depth. | 1 +lookahead.cube.fraction | double | adaptive fraction to create lookahead cubes. Used when lookahead.cube.cutoff is adaptive_freevars or adaptive_psat | 0.4 +lookahead.cube.freevars | double | cube free variable fraction. Used when lookahead.cube.cutoff is freevars | 0.8 +lookahead.cube.psat.clause_base | double | clause base for PSAT cutoff | 2 +lookahead.cube.psat.trigger | double | trigger value to create lookahead cubes for PSAT cutoff. Used when lookahead.cube.cutoff is psat | 5 +lookahead.cube.psat.var_exp | double | free variable exponent for PSAT cutoff | 1 +lookahead.delta_fraction | double | number between 0 and 1, the smaller the more literals are selected for double lookahead | 1.0 +lookahead.double | bool | enable doubld lookahead | true +lookahead.global_autarky | bool | prefer to branch on variables that occur in clauses that are reduced | false +lookahead.preselect | bool | use pre-selection of subset of variables for branching | false +lookahead.reward | symbol | select lookahead heuristic: ternary, heule_schur (Heule Schur), heuleu (Heule Unit), unit, or march_cu | march_cu +lookahead.use_learned | bool | use learned clauses when selecting lookahead literal | false +lookahead_scores | bool | extract lookahead scores. A utility that can only be used from the DIMACS front-end | false +lookahead_simplify | bool | use lookahead solver during simplification | false +lookahead_simplify.bca | bool | add learned binary clauses as part of lookahead simplification | true +max_conflicts | unsigned int | maximum number of conflicts | 4294967295 +max_memory | unsigned int | maximum amount of memory in megabytes | 4294967295 +minimize_lemmas | bool | minimize learned clauses | true +override_incremental | bool | override incremental safety gaps. Enable elimination of blocked clauses and variables even if solver is reused | false +pb.lemma_format | symbol | generate either cardinality or pb lemmas | cardinality +pb.min_arity | unsigned int | minimal arity to compile pb/cardinality constraints to CNF | 9 +pb.resolve | symbol | resolution strategy for boolean algebra solver: cardinality, rounding | cardinality +pb.solver | symbol | method for handling Pseudo-Boolean constraints: circuit (arithmetical circuit), sorting (sorting circuit), totalizer (use totalizer encoding), binary_merge, segmented, solver (use native solver) | solver +phase | symbol | phase selection strategy: always_false, always_true, basic_caching, random, caching | caching +phase.sticky | bool | use sticky phase caching | true +prob_search | bool | use probsat local search instead of CDCL | false +probing | bool | apply failed literal detection during simplification | true +probing_binary | bool | probe binary clauses | true +probing_cache | bool | add binary literals as lemmas | true +probing_cache_limit | unsigned int | cache binaries unless overall memory usage exceeds cache limit | 1024 +probing_limit | unsigned int | limit to the number of probe calls | 5000000 +propagate.prefetch | bool | prefetch watch lists for assigned literals | true +random_freq | double | frequency of random case splits | 0.01 +random_seed | unsigned int | random seed | 0 +reorder.activity_scale | unsigned int | scaling factor for activity update | 100 +reorder.base | unsigned int | number of conflicts per random reorder | 4294967295 +reorder.itau | double | inverse temperature for softmax | 4.0 +rephase.base | unsigned int | number of conflicts per rephase | 1000 +resolution.cls_cutoff1 | unsigned int | limit1 - total number of problems clauses for the second cutoff of Boolean variable elimination | 100000000 +resolution.cls_cutoff2 | unsigned int | limit2 - total number of problems clauses for the second cutoff of Boolean variable elimination | 700000000 +resolution.limit | unsigned int | approx. maximum number of literals visited during variable elimination | 500000000 +resolution.lit_cutoff_range1 | unsigned int | second cutoff (total number of literals) for Boolean variable elimination, for problems containing less than res_cls_cutoff1 clauses | 700 +resolution.lit_cutoff_range2 | unsigned int | second cutoff (total number of literals) for Boolean variable elimination, for problems containing more than res_cls_cutoff1 and less than res_cls_cutoff2 | 400 +resolution.lit_cutoff_range3 | unsigned int | second cutoff (total number of literals) for Boolean variable elimination, for problems containing more than res_cls_cutoff2 | 300 +resolution.occ_cutoff | unsigned int | first cutoff (on number of positive/negative occurrences) for Boolean variable elimination | 10 +resolution.occ_cutoff_range1 | unsigned int | second cutoff (number of positive/negative occurrences) for Boolean variable elimination, for problems containing less than res_cls_cutoff1 clauses | 8 +resolution.occ_cutoff_range2 | unsigned int | second cutoff (number of positive/negative occurrences) for Boolean variable elimination, for problems containing more than res_cls_cutoff1 and less than res_cls_cutoff2 | 5 +resolution.occ_cutoff_range3 | unsigned int | second cutoff (number of positive/negative occurrences) for Boolean variable elimination, for problems containing more than res_cls_cutoff2 | 3 +restart | symbol | restart strategy: static, luby, ema or geometric | ema +restart.emafastglue | double | ema alpha factor for fast moving average | 0.03 +restart.emaslowglue | double | ema alpha factor for slow moving average | 1e-05 +restart.factor | double | restart increment factor for geometric strategy | 1.5 +restart.fast | bool | use fast restart approach only removing less active literals. | true +restart.initial | unsigned int | initial restart (number of conflicts) | 2 +restart.margin | double | margin between fast and slow restart factors. For ema | 1.1 +restart.max | unsigned int | maximal number of restarts. | 4294967295 +retain_blocked_clauses | bool | retain blocked clauses as lemmas | true +scc | bool | eliminate Boolean variables by computing strongly connected components | true +scc.tr | bool | apply transitive reduction, eliminate redundant binary clauses | true +search.sat.conflicts | unsigned int | period for solving for sat (in number of conflicts) | 400 +search.unsat.conflicts | unsigned int | period for solving for unsat (in number of conflicts) | 400 +simplify.delay | unsigned int | set initial delay of simplification by a conflict count | 0 +subsumption | bool | eliminate subsumed clauses | true +subsumption.limit | unsigned int | approx. maximum number of literals visited during subsumption (and subsumption resolution) | 100000000 +threads | unsigned int | number of parallel threads to use | 1 +variable_decay | unsigned int | multiplier (divided by 100) for the VSIDS activity increment | 110 + +## Module solver + +Description: solver parameters + Parameter | Type | Description | Default + ----------|------|-------------|-------- +axioms2files | bool | print negated theory axioms to separate files during search | false +cancel_backup_file | symbol | file to save partial search state if search is canceled | +lemmas2console | bool | print lemmas during search | false +smtlib2_log | symbol | file to save solver interaction | +timeout | unsigned int | timeout on the solver object; overwrites a global timeout | 4294967295 + +## Module opt + +Description: optimization parameters + Parameter | Type | Description | Default + ----------|------|-------------|-------- +dump_benchmarks | bool | dump benchmarks for profiling | false +dump_models | bool | display intermediary models to stdout | false +elim_01 | bool | eliminate 01 variables | true +enable_core_rotate | bool | enable core rotation to both sample cores and correction sets | false +enable_lns | bool | enable LNS during weighted maxsat | false +enable_sat | bool | enable the new SAT core for propositional constraints | true +enable_sls | bool | enable SLS tuning during weighted maxsat | false +incremental | bool | set incremental mode. It disables pre-processing and enables adding constraints in model event handler | false +lns_conflicts | unsigned int | initial conflict count for LNS search | 1000 +maxlex.enable | bool | enable maxlex heuristic for lexicographic MaxSAT problems | true +maxres.add_upper_bound_block | bool | restict upper bound with constraint | false +maxres.hill_climb | bool | give preference for large weight cores | true +maxres.max_core_size | unsigned int | break batch of generated cores if size reaches this number | 3 +maxres.max_correction_set_size | unsigned int | allow generating correction set constraints up to maximal size | 3 +maxres.max_num_cores | unsigned int | maximal number of cores per round | 200 +maxres.maximize_assignment | bool | find an MSS/MCS to improve current assignment | false +maxres.pivot_on_correction_set | bool | reduce soft constraints if the current correction set is smaller than current core | true +maxres.wmax | bool | use weighted theory solver to constrain upper bounds | false +maxsat_engine | symbol | select engine for maxsat: 'core_maxsat', 'wmax', 'maxres', 'pd-maxres', 'maxres-bin', 'rc2' | maxres +optsmt_engine | symbol | select optimization engine: 'basic', 'symba' | basic +pb.compile_equality | bool | compile arithmetical equalities into pseudo-Boolean equality (instead of two inequalites) | false +pp.neat | bool | use neat (as opposed to less readable, but faster) pretty printer when displaying context | true +pp.wcnf | bool | print maxsat benchmark into wcnf format | false +priority | symbol | select how to priortize objectives: 'lex' (lexicographic), 'pareto', 'box' | lex +rc2.totalizer | bool | use totalizer for rc2 encoding | true +rlimit | unsigned int | resource limit (0 means no limit) | 0 +solution_prefix | symbol | path prefix to dump intermediary, but non-optimal, solutions | +timeout | unsigned int | timeout (in milliseconds) (UINT_MAX and 0 mean no timeout) | 4294967295 + +## Module parallel + +Description: parameters for parallel solver + Parameter | Type | Description | Default + ----------|------|-------------|-------- +conquer.backtrack_frequency | unsigned int | frequency to apply core minimization during conquer | 10 +conquer.batch_size | unsigned int | number of cubes to batch together for fast conquer | 100 +conquer.delay | unsigned int | delay of cubes until applying conquer | 10 +conquer.restart.max | unsigned int | maximal number of restarts during conquer phase | 5 +enable | bool | enable parallel solver by default on selected tactics (for QF_BV) | false +simplify.exp | double | restart and inprocess max is multiplied by simplify.exp ^ depth | 1 +simplify.inprocess.max | unsigned int | maximal number of inprocessing steps during simplification | 2 +simplify.max_conflicts | unsigned int | maximal number of conflicts during simplifcation phase | 4294967295 +simplify.restart.max | unsigned int | maximal number of restarts during simplification phase | 5000 +threads.max | unsigned int | caps maximal number of threads below the number of processors | 10000 + +## Module nnf + +Description: negation normal form + Parameter | Type | Description | Default + ----------|------|-------------|-------- +ignore_labels | bool | remove/ignore labels in the input formula, this option is ignored if proofs are enabled | false +max_memory | unsigned int | maximum amount of memory in megabytes | 4294967295 +mode | symbol | NNF translation mode: skolem (skolem normal form), quantifiers (skolem normal form + quantifiers in NNF), full | skolem +sk_hack | bool | hack for VCC | false + +## Module algebraic + +Description: real algebraic number package. Non-default parameter settings are not supported + Parameter | Type | Description | Default + ----------|------|-------------|-------- +factor | bool | use polynomial factorization to simplify polynomials representing algebraic numbers | true +factor_max_prime | unsigned int | parameter for the polynomial factorization procedure in the algebraic number module. Z3 polynomial factorization is composed of three steps: factorization in GF(p), lifting and search. This parameter limits the maximum prime number p to be used in the first step | 31 +factor_num_primes | unsigned int | parameter for the polynomial factorization procedure in the algebraic number module. Z3 polynomial factorization is composed of three steps: factorization in GF(p), lifting and search. The search space may be reduced by factoring the polynomial in different GF(p)'s. This parameter specify the maximum number of finite factorizations to be considered, before lifiting and searching | 1 +factor_search_size | unsigned int | parameter for the polynomial factorization procedure in the algebraic number module. Z3 polynomial factorization is composed of three steps: factorization in GF(p), lifting and search. This parameter can be used to limit the search space | 5000 +min_mag | unsigned int | Z3 represents algebraic numbers using a (square-free) polynomial p and an isolating interval (which contains one and only one root of p). This interval may be refined during the computations. This parameter specifies whether to cache the value of a refined interval or not. It says the minimal size of an interval for caching purposes is 1/2^16 | 16 +zero_accuracy | unsigned int | one of the most time-consuming operations in the real algebraic number module is determining the sign of a polynomial evaluated at a sample point with non-rational algebraic number values. Let k be the value of this option. If k is 0, Z3 uses precise computation. Otherwise, the result of a polynomial evaluation is considered to be 0 if Z3 can show it is inside the interval (-1/2^k, 1/2^k) | 0 + +## Module combined_solver + +Description: combines two solvers: non-incremental (solver1) and incremental (solver2) + Parameter | Type | Description | Default + ----------|------|-------------|-------- +ignore_solver1 | bool | if true, solver 2 is always used | false +solver2_timeout | unsigned int | fallback to solver 1 after timeout even when in incremental model | 4294967295 +solver2_unknown | unsigned int | what should be done when solver 2 returns unknown: 0 - just return unknown, 1 - execute solver 1 if quantifier free problem, 2 - execute solver 1 | 1 + +## Module rcf + +Description: real closed fields + Parameter | Type | Description | Default + ----------|------|-------------|-------- +clean_denominators | bool | clean denominators before root isolation | true +inf_precision | unsigned int | a value k that is the initial interval size (i.e., (0, 1/2^l)) used as an approximation for infinitesimal values | 24 +initial_precision | unsigned int | a value k that is the initial interval size (as 1/2^k) when creating transcendentals and approximated division | 24 +lazy_algebraic_normalization | bool | during sturm-seq and square-free polynomial computations, only normalize algebraic polynomial expressions when the defining polynomial is monic | true +max_precision | unsigned int | during sign determination we switch from interval arithmetic to complete methods when the interval size is less than 1/2^k, where k is the max_precision | 128 +use_prem | bool | use pseudo-remainder instead of remainder when computing GCDs and Sturm-Tarski sequences | true +ERROR: unknown module 'rewriter, description: new formula simplification module used in the tactic framework' + +## Module ackermannization + +Description: solving UF via ackermannization + Parameter | Type | Description | Default + ----------|------|-------------|-------- +eager | bool | eagerly instantiate all congruence rules | true +inc_sat_backend | bool | use incremental SAT | false +sat_backend | bool | use SAT rather than SMT in qfufbv_ackr_tactic | false + +## Module nlsat + +Description: nonlinear solver + Parameter | Type | Description | Default + ----------|------|-------------|-------- +check_lemmas | bool | check lemmas on the fly using an independent nlsat solver | false +factor | bool | factor polynomials produced during conflict resolution. | true +inline_vars | bool | inline variables that can be isolated from equations (not supported in incremental mode) | false +lazy | unsigned int | how lazy the solver is. | 0 +log_lemmas | bool | display lemmas as self-contained SMT formulas | false +max_conflicts | unsigned int | maximum number of conflicts. | 4294967295 +max_memory | unsigned int | maximum amount of memory in megabytes | 4294967295 +minimize_conflicts | bool | minimize conflicts | false +randomize | bool | randomize selection of a witness in nlsat. | true +reorder | bool | reorder variables. | true +seed | unsigned int | random seed. | 0 +shuffle_vars | bool | use a random variable order. | false +simplify_conflicts | bool | simplify conflicts using equalities before resolving them in nlsat solver. | true + + +## Module fp + +Description: fixedpoint parameters + Parameter | Type | Description | Default + ----------|------|-------------|-------- +bmc.linear_unrolling_depth | unsigned int | Maximal level to explore | 4294967295 +datalog.all_or_nothing_deltas | bool | compile rules so that it is enough for the delta relation in union and widening operations to determine only whether the updated relation was modified or not | false +datalog.check_relation | symbol | name of default relation to check. operations on the default relation will be verified using SMT solving | null +datalog.compile_with_widening | bool | widening will be used to compile recursive rules | false +datalog.dbg_fpr_nonempty_relation_signature | bool | if true, finite_product_relation will attempt to avoid creating inner relation with empty signature by putting in half of the table columns, if it would have been empty otherwise | false +datalog.default_relation | symbol | default relation implementation: external_relation, pentagon | pentagon +datalog.default_table | symbol | default table implementation: sparse, hashtable, bitvector, interval | sparse +datalog.default_table_checked | bool | if true, the default table will be default_table inside a wrapper that checks that its results are the same as of default_table_checker table | false +datalog.default_table_checker | symbol | see default_table_checked | null +datalog.explanations_on_relation_level | bool | if true, explanations are generated as history of each relation, rather than per fact (generate_explanations must be set to true for this option to have any effect) | false +datalog.generate_explanations | bool | produce explanations for produced facts when using the datalog engine | false +datalog.initial_restart_timeout | unsigned int | length of saturation run before the first restart (in ms), zero means no restarts | 0 +datalog.magic_sets_for_queries | bool | magic set transformation will be used for queries | false +datalog.output_profile | bool | determines whether profile information should be output when outputting Datalog rules or instructions | false +datalog.print.tuples | bool | determines whether tuples for output predicates should be output | true +datalog.profile_timeout_milliseconds | unsigned int | instructions and rules that took less than the threshold will not be printed when printed the instruction/rule list | 0 +datalog.similarity_compressor | bool | rules that differ only in values of constants will be merged into a single rule | true +datalog.similarity_compressor_threshold | unsigned int | if similarity_compressor is on, this value determines how many similar rules there must be in order for them to be merged | 11 +datalog.subsumption | bool | if true, removes/filters predicates with total transitions | true +datalog.timeout | unsigned int | Time limit used for saturation | 0 +datalog.unbound_compressor | bool | auxiliary relations will be introduced to avoid unbound variables in rule heads | true +datalog.use_map_names | bool | use names from map files when displaying tuples | true +engine | symbol | Select: auto-config, datalog, bmc, spacer | auto-config +generate_proof_trace | bool | trace for 'sat' answer as proof object | false +print_aig | symbol | Dump clauses in AIG text format (AAG) to the given file name | +print_answer | bool | print answer instance(s) to query | false +print_boogie_certificate | bool | print certificate for reachability or non-reachability using a format understood by Boogie | false +print_certificate | bool | print certificate for reachability or non-reachability | false +print_fixedpoint_extensions | bool | use SMT-LIB2 fixedpoint extensions, instead of pure SMT2, when printing rules | true +print_low_level_smt2 | bool | use (faster) low-level SMT2 printer (the printer is scalable but the result may not be as readable) | false +print_statistics | bool | print statistics | false +print_with_variable_declarations | bool | use variable declarations when displaying rules (instead of attempting to use original names) | true +spacer.arith.solver | unsigned int | arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver | 2 +spacer.blast_term_ite_inflation | unsigned int | Maximum inflation for non-Boolean ite-terms expansion: 0 (none), k (multiplicative) | 3 +spacer.ctp | bool | Enable counterexample-to-pushing | true +spacer.dump_benchmarks | bool | Dump SMT queries as benchmarks | false +spacer.dump_threshold | double | Threshold in seconds on dumping benchmarks | 5.0 +spacer.elim_aux | bool | Eliminate auxiliary variables in reachability facts | true +spacer.eq_prop | bool | Enable equality and bound propagation in arithmetic | true +spacer.gpdr | bool | Use GPDR solving strategy for non-linear CHC | false +spacer.gpdr.bfs | bool | Use BFS exploration strategy for expanding model search | true +spacer.ground_pobs | bool | Ground pobs by using values from a model | true +spacer.iuc | unsigned int | 0 = use old implementation of unsat-core-generation, 1 = use new implementation of IUC generation, 2 = use new implementation of IUC + min-cut optimization | 1 +spacer.iuc.arith | unsigned int | 0 = use simple Farkas plugin, 1 = use simple Farkas plugin with constant from other partition (like old unsat-core-generation),2 = use Gaussian elimination optimization (broken), 3 = use additive IUC plugin | 1 +spacer.iuc.debug_proof | bool | prints proof used by unsat-core-learner for debugging purposes (debugging) | false +spacer.iuc.old_hyp_reducer | bool | use old hyp reducer instead of new implementation, for debugging only | false +spacer.iuc.print_farkas_stats | bool | prints for each proof how many Farkas lemmas it contains and how many of these participate in the cut (for debugging) | false +spacer.iuc.split_farkas_literals | bool | Split Farkas literals | false +spacer.keep_proxy | bool | keep proxy variables (internal parameter) | true +spacer.logic | symbol | SMT-LIB logic to configure internal SMT solvers | +spacer.max_level | unsigned int | Maximum level to explore | 4294967295 +spacer.max_num_contexts | unsigned int | maximal number of contexts to create | 500 +spacer.mbqi | bool | Enable mbqi | true +spacer.min_level | unsigned int | Minimal level to explore | 0 +spacer.native_mbp | bool | Use native mbp of Z3 | true +spacer.order_children | unsigned int | SPACER: order of enqueuing children in non-linear rules : 0 (original), 1 (reverse), 2 (random) | 0 +spacer.p3.share_invariants | bool | Share invariants lemmas | false +spacer.p3.share_lemmas | bool | Share frame lemmas | false +spacer.print_json | symbol | Print pobs tree in JSON format to a given file | +spacer.propagate | bool | Enable propagate/pushing phase | true +spacer.push_pob | bool | push blocked pobs to higher level | false +spacer.push_pob_max_depth | unsigned int | Maximum depth at which push_pob is enabled | 4294967295 +spacer.q3 | bool | Allow quantified lemmas in frames | true +spacer.q3.instantiate | bool | Instantiate quantified lemmas | true +spacer.q3.qgen.normalize | bool | normalize cube before quantified generalization | true +spacer.q3.use_qgen | bool | use quantified lemma generalizer | false +spacer.random_seed | unsigned int | Random seed to be used by SMT solver | 0 +spacer.reach_dnf | bool | Restrict reachability facts to DNF | true +spacer.reset_pob_queue | bool | SPACER: reset pob obligation queue when entering a new level | true +spacer.restart_initial_threshold | unsigned int | Initial threshold for restarts | 10 +spacer.restarts | bool | Enable resetting obligation queue | false +spacer.simplify_lemmas_post | bool | simplify derived lemmas after inductive propagation | false +spacer.simplify_lemmas_pre | bool | simplify derived lemmas before inductive propagation | false +spacer.simplify_pob | bool | simplify pobs by removing redundant constraints | false +spacer.trace_file | symbol | Log file for progress events | +spacer.use_array_eq_generalizer | bool | SPACER: attempt to generalize lemmas with array equalities | true +spacer.use_bg_invs | bool | Enable external background invariants | false +spacer.use_derivations | bool | SPACER: using derivation mechanism to cache intermediate results for non-linear rules | true +spacer.use_euf_gen | bool | Generalize lemmas and pobs using implied equalities | false +spacer.use_inc_clause | bool | Use incremental clause to represent trans | true +spacer.use_inductive_generalizer | bool | generalize lemmas using induction strengthening | true +spacer.use_lemma_as_cti | bool | SPACER: use a lemma instead of a CTI in flexible_trace | false +spacer.use_lim_num_gen | bool | Enable limit numbers generalizer to get smaller numbers | false +spacer.validate_lemmas | bool | Validate each lemma after generalization | false +spacer.weak_abs | bool | Weak abstraction | true +tab.selection | symbol | selection method for tabular strategy: weight (default), first, var-use | weight +validate | bool | validate result (by proof checking or model checking) | false +xform.array_blast | bool | try to eliminate local array terms using Ackermannization -- some array terms may remain | false +xform.array_blast_full | bool | eliminate all local array variables by QE | false +xform.bit_blast | bool | bit-blast bit-vectors | false +xform.coalesce_rules | bool | coalesce rules | false +xform.coi | bool | use cone of influence simplification | true +xform.compress_unbound | bool | compress tails with unbound variables | true +xform.elim_term_ite | bool | Eliminate term-ite expressions | false +xform.elim_term_ite.inflation | unsigned int | Maximum inflation for non-Boolean ite-terms blasting: 0 (none), k (multiplicative) | 3 +xform.fix_unbound_vars | bool | fix unbound variables in tail | false +xform.inline_eager | bool | try eager inlining of rules | true +xform.inline_linear | bool | try linear inlining method | true +xform.inline_linear_branch | bool | try linear inlining method with potential expansion | false +xform.instantiate_arrays | bool | Transforms P(a) into P(i, a[i] a) | false +xform.instantiate_arrays.enforce | bool | Transforms P(a) into P(i, a[i]), discards a from predicate | false +xform.instantiate_arrays.nb_quantifier | unsigned int | Gives the number of quantifiers per array | 1 +xform.instantiate_arrays.slice_technique | symbol | => GetId(i) = i, => GetId(i) = true | no-slicing +xform.instantiate_quantifiers | bool | instantiate quantified Horn clauses using E-matching heuristic | false +xform.magic | bool | perform symbolic magic set transformation | false +xform.quantify_arrays | bool | create quantified Horn clauses from clauses with arrays | false +xform.scale | bool | add scaling variable to linear real arithmetic clauses | false +xform.slice | bool | simplify clause set using slicing | true +xform.subsumption_checker | bool | Enable subsumption checker (no support for model conversion) | true +xform.tail_simplifier_pve | bool | propagate_variable_equivalences | true +xform.transform_arrays | bool | Rewrites arrays equalities and applies select over store | false +xform.unfold_rules | unsigned int | unfold rules statically using iterative squaring | 0 + +## Module smt + +Description: smt solver based on lazy smt + Parameter | Type | Description | Default + ----------|------|-------------|-------- +arith.auto_config_simplex | bool | force simplex solver in auto_config | false +arith.bprop_on_pivoted_rows | bool | propagate bounds on rows changed by the pivot operation | true +arith.branch_cut_ratio | unsigned int | branch/cut ratio for linear integer arithmetic | 2 +arith.dump_lemmas | bool | dump arithmetic theory lemmas to files | false +arith.eager_eq_axioms | bool | eager equality axioms | true +arith.enable_hnf | bool | enable hnf (Hermite Normal Form) cuts | true +arith.greatest_error_pivot | bool | Pivoting strategy | false +arith.ignore_int | bool | treat integer variables as real | false +arith.int_eq_branch | bool | branching using derived integer equations | false +arith.min | bool | minimize cost | false +arith.nl | bool | (incomplete) nonlinear arithmetic support based on Groebner basis and interval propagation, relevant only if smt.arith.solver=2 | true +arith.nl.branching | bool | branching on integer variables in non linear clusters, relevant only if smt.arith.solver=2 | true +arith.nl.delay | unsigned int | number of calls to final check before invoking bounded nlsat check | 500 +arith.nl.expp | bool | expensive patching | false +arith.nl.gr_q | unsigned int | grobner's quota | 10 +arith.nl.grobner | bool | run grobner's basis heuristic | true +arith.nl.grobner_cnfl_to_report | unsigned int | grobner's maximum number of conflicts to report | 1 +arith.nl.grobner_eqs_growth | unsigned int | grobner's number of equalities growth | 10 +arith.nl.grobner_expr_degree_growth | unsigned int | grobner's maximum expr degree growth | 2 +arith.nl.grobner_expr_size_growth | unsigned int | grobner's maximum expr size growth | 2 +arith.nl.grobner_frequency | unsigned int | grobner's call frequency | 4 +arith.nl.grobner_max_simplified | unsigned int | grobner's maximum number of simplifications | 10000 +arith.nl.grobner_subs_fixed | unsigned int | 0 - no subs, 1 - substitute, 2 - substitute fixed zeros only | 1 +arith.nl.horner | bool | run horner's heuristic | true +arith.nl.horner_frequency | unsigned int | horner's call frequency | 4 +arith.nl.horner_row_length_limit | unsigned int | row is disregarded by the heuristic if its length is longer than the value | 10 +arith.nl.horner_subs_fixed | unsigned int | 0 - no subs, 1 - substitute, 2 - substitute fixed zeros only | 2 +arith.nl.nra | bool | call nra_solver when incremental linearization does not produce a lemma, this option is ignored when arith.nl=false, relevant only if smt.arith.solver=6 | true +arith.nl.order | bool | run order lemmas | true +arith.nl.rounds | unsigned int | threshold for number of (nested) final checks for non linear arithmetic, relevant only if smt.arith.solver=2 | 1024 +arith.nl.tangents | bool | run tangent lemmas | true +arith.print_ext_var_names | bool | print external variable names | false +arith.print_stats | bool | print statistic | false +arith.propagate_eqs | bool | propagate (cheap) equalities | true +arith.propagation_mode | unsigned int | 0 - no propagation, 1 - propagate existing literals, 2 - refine finite bounds | 1 +arith.random_initial_value | bool | use random initial values in the simplex-based procedure for linear arithmetic | false +arith.rep_freq | unsigned int | the report frequency, in how many iterations print the cost and other info | 0 +arith.simplex_strategy | unsigned int | simplex strategy for the solver | 0 +arith.solver | unsigned int | arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver | 6 +array.extensional | bool | extensional array theory | true +array.weak | bool | weak array theory | false +auto_config | bool | automatically configure solver | true +bv.delay | bool | delay internalize expensive bit-vector operations | true +bv.enable_int2bv | bool | enable support for int2bv and bv2int operators | true +bv.eq_axioms | bool | enable redundant equality axioms for bit-vectors | true +bv.reflect | bool | create enode for every bit-vector term | true +bv.watch_diseq | bool | use watch lists instead of eager axioms for bit-vectors | false +candidate_models | bool | create candidate models even when quantifier or theory reasoning is incomplete | false +case_split | unsigned int | 0 - case split based on variable activity, 1 - similar to 0, but delay case splits created during the search, 2 - similar to 0, but cache the relevancy, 3 - case split based on relevancy (structural splitting), 4 - case split on relevancy and activity, 5 - case split on relevancy and current goal, 6 - activity-based case split with theory-aware branching activity | 1 +clause_proof | bool | record a clausal proof | false +core.extend_nonlocal_patterns | bool | extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier's body | false +core.extend_patterns | bool | extend unsat core with literals that trigger (potential) quantifier instances | false +core.extend_patterns.max_distance | unsigned int | limits the distance of a pattern-extended unsat core | 4294967295 +core.minimize | bool | minimize unsat core produced by SMT context | false +core.validate | bool | [internal] validate unsat core produced by SMT context. This option is intended for debugging | false +cube_depth | unsigned int | cube depth. | 1 +dack | unsigned int | 0 - disable dynamic ackermannization, 1 - expand Leibniz's axiom if a congruence is the root of a conflict, 2 - expand Leibniz's axiom if a congruence is used during conflict resolution | 1 +dack.eq | bool | enable dynamic ackermannization for transtivity of equalities | false +dack.factor | double | number of instance per conflict | 0.1 +dack.gc | unsigned int | Dynamic ackermannization garbage collection frequency (per conflict) | 2000 +dack.gc_inv_decay | double | Dynamic ackermannization garbage collection decay | 0.8 +dack.threshold | unsigned int | number of times the congruence rule must be used before Leibniz's axiom is expanded | 10 +delay_units | bool | if true then z3 will not restart when a unit clause is learned | false +delay_units_threshold | unsigned int | maximum number of learned unit clauses before restarting, ignored if delay_units is false | 32 +dt_lazy_splits | unsigned int | How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy | 1 +ematching | bool | E-Matching based quantifier instantiation | true +induction | bool | enable generation of induction lemmas | false +lemma_gc_strategy | unsigned int | lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none | 0 +logic | symbol | logic used to setup the SMT solver | +macro_finder | bool | try to find universally quantified formulas that can be viewed as macros | false +max_conflicts | unsigned int | maximum number of conflicts before giving up. | 4294967295 +mbqi | bool | model based quantifier instantiation (MBQI) | true +mbqi.force_template | unsigned int | some quantifiers can be used as templates for building interpretations for functions. Z3 uses heuristics to decide whether a quantifier will be used as a template or not. Quantifiers with weight >= mbqi.force_template are forced to be used as a template | 10 +mbqi.id | string | Only use model-based instantiation for quantifiers with id's beginning with string | +mbqi.max_cexs | unsigned int | initial maximal number of counterexamples used in MBQI, each counterexample generates a quantifier instantiation | 1 +mbqi.max_cexs_incr | unsigned int | increment for MBQI_MAX_CEXS, the increment is performed after each round of MBQI | 0 +mbqi.max_iterations | unsigned int | maximum number of rounds of MBQI | 1000 +mbqi.trace | bool | generate tracing messages for Model Based Quantifier Instantiation (MBQI). It will display a message before every round of MBQI, and the quantifiers that were not satisfied | false +pb.conflict_frequency | unsigned int | conflict frequency for Pseudo-Boolean theory | 1000 +pb.learn_complements | bool | learn complement literals for Pseudo-Boolean theory | true +phase_caching_off | unsigned int | number of conflicts while phase caching is off | 100 +phase_caching_on | unsigned int | number of conflicts while phase caching is on | 400 +phase_selection | unsigned int | phase selection heuristic: 0 - always false, 1 - always true, 2 - phase caching, 3 - phase caching conservative, 4 - phase caching conservative 2, 5 - random, 6 - number of occurrences, 7 - theory | 3 +pull_nested_quantifiers | bool | pull nested quantifiers | false +q.lift_ite | unsigned int | 0 - don not lift non-ground if-then-else, 1 - use conservative ite lifting, 2 - use full lifting of if-then-else under quantifiers | 0 +q.lite | bool | Use cheap quantifier elimination during pre-processing | false +qi.cost | string | expression specifying what is the cost of a given quantifier instantiation | (+ weight generation) +qi.eager_threshold | double | threshold for eager quantifier instantiation | 10.0 +qi.lazy_threshold | double | threshold for lazy quantifier instantiation | 20.0 +qi.max_instances | unsigned int | maximum number of quantifier instantiations | 4294967295 +qi.max_multi_patterns | unsigned int | specify the number of extra multi patterns | 0 +qi.profile | bool | profile quantifier instantiation | false +qi.profile_freq | unsigned int | how frequent results are reported by qi.profile | 4294967295 +qi.quick_checker | unsigned int | specify quick checker mode, 0 - no quick checker, 1 - using unsat instances, 2 - using both unsat and no-sat instances | 0 +quasi_macros | bool | try to find universally quantified formulas that are quasi-macros | false +random_seed | unsigned int | random seed for the smt solver | 0 +refine_inj_axioms | bool | refine injectivity axioms | true +relevancy | unsigned int | relevancy propagation heuristic: 0 - disabled, 1 - relevancy is tracked by only affects quantifier instantiation, 2 - relevancy is tracked, and an atom is only asserted if it is relevant | 2 +restart.max | unsigned int | maximal number of restarts. | 4294967295 +restart_factor | double | when using geometric (or inner-outer-geometric) progression of restarts, it specifies the constant used to multiply the current restart threshold | 1.1 +restart_strategy | unsigned int | 0 - geometric, 1 - inner-outer-geometric, 2 - luby, 3 - fixed, 4 - arithmetic | 1 +restricted_quasi_macros | bool | try to find universally quantified formulas that are restricted quasi-macros | false +seq.max_unfolding | unsigned int | maximal unfolding depth for checking string equations and regular expressions | 1000000000 +seq.split_w_len | bool | enable splitting guided by length constraints | true +seq.validate | bool | enable self-validation of theory axioms created by seq theory | false +str.aggressive_length_testing | bool | prioritize testing concrete length values over generating more options | false +str.aggressive_unroll_testing | bool | prioritize testing concrete regex unroll counts over generating more options | true +str.aggressive_value_testing | bool | prioritize testing concrete string constant values over generating more options | false +str.fast_length_tester_cache | bool | cache length tester constants instead of regenerating them | false +str.fast_value_tester_cache | bool | cache value tester constants instead of regenerating them | true +str.fixed_length_naive_cex | bool | construct naive counterexamples when fixed-length model construction fails for a given length assignment (Z3str3 only) | true +str.fixed_length_refinement | bool | use abstraction refinement in fixed-length equation solver (Z3str3 only) | false +str.overlap_priority | double | theory-aware priority for overlapping variable cases; use smt.theory_aware_branching=true | -0.1 +str.regex_automata_difficulty_threshold | unsigned int | difficulty threshold for regex automata heuristics | 1000 +str.regex_automata_failed_automaton_threshold | unsigned int | number of failed automaton construction attempts after which a full automaton is automatically built | 10 +str.regex_automata_failed_intersection_threshold | unsigned int | number of failed automaton intersection attempts after which intersection is always computed | 10 +str.regex_automata_intersection_difficulty_threshold | unsigned int | difficulty threshold for regex intersection heuristics | 1000 +str.regex_automata_length_attempt_threshold | unsigned int | number of length/path constraint attempts before checking unsatisfiability of regex terms | 10 +str.string_constant_cache | bool | cache all generated string constants generated from anywhere in theory_str | true +str.strong_arrangements | bool | assert equivalences instead of implications when generating string arrangement axioms | true +string_solver | symbol | solver for string/sequence theories. options are: 'z3str3' (specialized string solver), 'seq' (sequence solver), 'auto' (use static features to choose best solver), 'empty' (a no-op solver that forces an answer unknown if strings were used), 'none' (no solver) | seq +theory_aware_branching | bool | Allow the context to use extra information from theory solvers regarding literal branching prioritization. | false +theory_case_split | bool | Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead. | false +threads | unsigned int | maximal number of parallel threads. | 1 +threads.cube_frequency | unsigned int | frequency for using cubing | 2 +threads.max_conflicts | unsigned int | maximal number of conflicts between rounds of cubing for parallel SMT | 400 + +## Module sls + +Description: Experimental Stochastic Local Search Solver (for QFBV only). + Parameter | Type | Description | Default + ----------|------|-------------|-------- +early_prune | bool | use early pruning for score prediction | true +max_memory | unsigned int | maximum amount of memory in megabytes | 4294967295 +max_restarts | unsigned int | maximum number of restarts | 4294967295 +paws_init | unsigned int | initial/minimum assertion weights | 40 +paws_sp | unsigned int | smooth assertion weights with probability paws_sp / 1024 | 52 +random_offset | bool | use random offset for candidate evaluation | true +random_seed | unsigned int | random seed | 0 +rescore | bool | rescore/normalize top-level score every base restart interval | true +restart_base | unsigned int | base restart interval given by moves per run | 100 +restart_init | bool | initialize to 0 or random value (= 1) after restart | false +scale_unsat | double | scale score of unsat expressions by this factor | 0.5 +track_unsat | bool | keep a list of unsat assertions as done in SAT - currently disabled internally | false +vns_mc | unsigned int | in local minima, try Monte Carlo sampling vns_mc many 2-bit-flips per bit | 0 +vns_repick | bool | in local minima, try picking a different assertion (only for walksat) | false +walksat | bool | use walksat assertion selection (instead of gsat) | true +walksat_repick | bool | repick assertion if randomizing in local minima | true +walksat_ucb | bool | use bandit heuristic for walksat assertion selection (instead of random) | true +walksat_ucb_constant | double | the ucb constant c in the term score + c * f(touched) | 20.0 +walksat_ucb_forget | double | scale touched by this factor every base restart interval | 1.0 +walksat_ucb_init | bool | initialize total ucb touched to formula size | false +walksat_ucb_noise | double | add noise 0 <= 256 * ucb_noise to ucb score for assertion selection | 0.0002 +wp | unsigned int | random walk with probability wp / 1024 | 100 diff --git a/README.md b/README.md index 2d75c73d6..39b0a3e78 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,19 @@ Z3 has a build system using CMake. Read the [README-CMake.md](README-CMake.md) file for details. It is recommended for most build tasks, except for building OCaml bindings. +## Building Z3 using vcpkg + +vcpkg is a full platform package manager, you can easily install libzmq with vcpkg. + +Execute: + +```bash +git clone https://github.com/microsoft/vcpkg.git +./bootstrap-vcpkg.bat # For powershell +./bootstrap-vcpkg.sh # For bash +./vcpkg install z3 +``` + ## Dependencies Z3 itself has few dependencies. It uses C++ runtime libraries, including pthreads for multi-threading. It is optionally possible to use GMP for multi-precision integers, but Z3 contains its own self-contained diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a2c1dc84d..b1fe88e06 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,9 +10,48 @@ Version 4.next - native word level bit-vector solving. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. +Version 4.11.2 +============== +- add error handling to fromString method in JavaScript +- fix regression in default parameters for CDCL, thanks to Nuno Lopes +- fix model evaluation bugs for as-array nested under functions (data-type constructors) +- add rewrite simplifications for datatypes with a single constructor +- add "Global Guidance" capability to SPACER, thanks to Arie Gurfinkel and Hari Gorvind. + The commit logs related to Global Guidance contain detailed information. +- change proof logging format for the new core to use SMTLIB commands. + The format was so far an extension of DRAT used by SAT solvers, but not well compatible + with SMT format that is extensible. The resulting format is a mild extension of SMTLIB with + three extra commands assume, learn, del. They track input clauses, generated clauses and deleted clauses. + They are optionally augmented by proof hints. Two proof hints are used in the current version: "rup" and "farkas". + "rup" is used whent the generated clause can be justified by reverse unit propagation. "farkas" is used when + the clause can be justified by a combination of Farkas cutting planes. There is a built-in proof checker for the + format. Quantifier instantiations are also tracked as proof hints. + Other proof hints are to be added as the feature set is tested and developed. The fallback, buit-in, + self-checker uses z3 to check that the generated clause is a consequence. Note that this is generally + insufficient as generated clauses are in principle required to only be satisfiability preserving. + Proof checking and tranformation operations is overall open ended. + The log for the first commit introducing this change contains further information on the format. +- fix to re-entrancy bug in user propagator (thanks to Clemens Eisenhofer). +- handle _toExpr for quantified formulas in JS bindings + +Version 4.11.1 +============== +- skipped + Version 4.11.0 ============== - remove `Z3_bool`, `Z3_TRUE`, `Z3_FALSE` from the API. Use `bool`, `true`, `false` instead. +- z3++.h no longer includes `` as it did not use it. +- add solver.axioms2files + - prints negated theory axioms to files. Each file should be unsat +- add solver.lemmas2console + - prints lemmas to the console. +- remove option smt.arith.dump_lemmas. It is replaced by solver.axioms2files +- add option smt.bv.reduce_size. + - it allows to apply incremental pre-processing of bit-vectors by identifying ranges that are known to be constant. + This rewrite is beneficial, for instance, when bit-vectors are constrained to have many high-level bits set to 0. +- add feature to model-based projection for arithmetic to handle integer division. +- add fromString method to JavaScript solver object. Version 4.10.2 ============== diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d29a3dd51..f338a5d98 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,7 @@ jobs: - job: "LinuxPythonDebug" displayName: "Ubuntu build - python make - debug" pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" strategy: matrix: MT: @@ -102,7 +102,7 @@ jobs: displayName: "Ubuntu build - cmake" condition: eq(0,1) pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" strategy: matrix: msanClang: @@ -135,7 +135,7 @@ jobs: - job: "UbuntuCMake" displayName: "Ubuntu build - cmake" pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" strategy: matrix: releaseClang: diff --git a/doc/mk_api_doc.py b/doc/mk_api_doc.py index 670634e07..6e2ef96e5 100644 --- a/doc/mk_api_doc.py +++ b/doc/mk_api_doc.py @@ -24,6 +24,7 @@ JS_API_PATH='../src/api/js' Z3PY_ENABLED=True DOTNET_ENABLED=True JAVA_ENABLED=True +Z3OPTIONS_ENABLED=True DOTNET_API_SEARCH_PATHS=['../src/api/dotnet'] JAVA_API_SEARCH_PATHS=['../src/api/java'] SCRIPT_DIR=os.path.abspath(os.path.dirname(__file__)) @@ -237,6 +238,7 @@ try: else: print('Javascript documentation disabled') + doxygen_config_file = temp_path('z3api.cfg') configure_file( doc_path('z3api.cfg.in'), diff --git a/doc/mk_params_doc.py b/doc/mk_params_doc.py new file mode 100644 index 000000000..de5276416 --- /dev/null +++ b/doc/mk_params_doc.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation 2015 +""" +Z3 API documentation for parameters +""" + +import argparse +import subprocess +import sys +import re +import os + +BUILD_DIR='../build' +OUTPUT_DIRECTORY=os.path.join(os.getcwd(), 'api') + +def parse_options(): + global BUILD_DIR, OUTPUT_DIRECTORY + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('-b', + '--build', + default=BUILD_DIR, + help='Directory where Z3 is built (default: %(default)s)', + ) + parser.add_argument('--output-dir', + dest='output_dir', + default=OUTPUT_DIRECTORY, + help='Path to output directory (default: %(default)s)', + ) + + pargs = parser.parse_args() + BUILD_DIR = pargs.build + OUTPUT_DIRECTORY = pargs.output_dir + +def help(ous): + global BUILD_DIR + ous.write("Z3 Options\n") + z3_exe = BUILD_DIR + "/z3" + out = subprocess.Popen([z3_exe, "-pm"],stdout=subprocess.PIPE).communicate()[0] + modules = ["global"] + if out != None: + out = out.decode(sys.stdout.encoding) + module_re = re.compile(r"\[module\] (.*)\,") + lines = out.split("\n") + for line in lines: + m = module_re.search(line) + if m: + modules += [m.group(1)] + for module in modules: + out = subprocess.Popen([z3_exe, "-pmmd:%s" % module],stdout=subprocess.PIPE).communicate()[0] + if out == None: + continue + out = out.decode(sys.stdout.encoding) + out = out.replace("\r","") + ous.write(out) + +parse_options() + +def mk_dir(d): + if not os.path.exists(d): + os.makedirs(d) + +mk_dir(os.path.join(OUTPUT_DIRECTORY, 'md')) + +with open(OUTPUT_DIRECTORY + "/md/Parameters.md",'w') as ous: + help(ous) diff --git a/examples/c++/example.cpp b/examples/c++/example.cpp index eb6d2c19b..06f3ffe3e 100644 --- a/examples/c++/example.cpp +++ b/examples/c++/example.cpp @@ -5,6 +5,7 @@ Copyright (c) 2015 Microsoft Corporation --*/ #include +#include #include #include"z3++.h" diff --git a/examples/c/test_capi.c b/examples/c/test_capi.c index a6fcbdb22..6e6bf84fe 100644 --- a/examples/c/test_capi.c +++ b/examples/c/test_capi.c @@ -2946,6 +2946,28 @@ void mk_model_example() { Z3_del_context(ctx); } +void divides_example() +{ + Z3_context ctx; + Z3_solver s; + Z3_ast x, number; + Z3_ast c; + + ctx = mk_context(); + s = mk_solver(ctx); + + x = mk_int_var(ctx, "x"); + number = mk_int(ctx, 2); + + c = Z3_mk_divides(ctx, number, x); + Z3_solver_assert(ctx, s, c); + + check2(ctx, s, Z3_L_TRUE); + + del_solver(ctx, s); + Z3_del_context(ctx); +} + /**@}*/ /**@}*/ @@ -2955,6 +2977,7 @@ int main() { #ifdef LOG_Z3_CALLS Z3_open_log("z3.log"); #endif + divides_example(); display_version(); simple_example(); demorgan(); diff --git a/examples/python/simplify_formula.py b/examples/python/simplify_formula.py new file mode 100644 index 000000000..4aff993b6 --- /dev/null +++ b/examples/python/simplify_formula.py @@ -0,0 +1,83 @@ +from z3 import * + +def is_atom(t): + if not is_bool(t): + return False + if not is_app(t): + return False + k = t.decl().kind() + if k == Z3_OP_AND or k == Z3_OP_OR or k == Z3_OP_IMPLIES: + return False + if k == Z3_OP_EQ and t.arg(0).is_bool(): + return False + if k == Z3_OP_TRUE or k == Z3_OP_FALSE or k == Z3_OP_XOR or k == Z3_OP_NOT: + return False + return True + +def atoms(fml): + visited = set([]) + atms = set([]) + def atoms_rec(t, visited, atms): + if t in visited: + return + visited |= { t } + if is_atom(t): + atms |= { t } + for s in t.children(): + atoms_rec(s, visited, atms) + atoms_rec(fml, visited, atms) + return atms + +def atom2literal(m, a): + if is_true(m.eval(a)): + return a + return Not(a) + +# Extract subset of atoms used to satisfy the negation +# of a formula. +# snot is a solver for Not(fml) +# s is a solver for fml +# m is a model for Not(fml) +# evaluate each atom in fml using m and create +# literals corresponding to the sign of the evaluation. +# If the model evaluates atoms to false, the literal is +# negated. +# +# +def implicant(atoms, s, snot): + m = snot.model() + lits = [atom2literal(m, a) for a in atoms] + is_sat = s.check(lits) + assert is_sat == unsat + core = s.unsat_core() + return Or([mk_not(c) for c in core]) + +# +# Extract a CNF representation of fml +# The procedure uses two solvers +# Enumerate models for Not(fml) +# Use the enumerated model to identify literals +# that imply Not(fml) +# The CNF of fml is a conjunction of the +# negation of these literals. +# + +def to_cnf(fml): + atms = atoms(fml) + s = Solver() + snot = Solver() + snot.add(Not(fml)) + s.add(fml) + + while sat == snot.check(): + clause = implicant(atms, s, snot) + yield clause + snot.add(clause) + + +a, b, c, = Bools('a b c') +fml = Or(And(a, b), And(Not(a), c)) + +for clause in to_cnf(fml): + print(clause) + diff --git a/examples/tptp/tptp5.cpp b/examples/tptp/tptp5.cpp index fac922db8..4f1d25aa9 100644 --- a/examples/tptp/tptp5.cpp +++ b/examples/tptp/tptp5.cpp @@ -15,6 +15,7 @@ Copyright (c) 2015 Microsoft Corporation #include #include #include +#include #include #include #include "z3++.h" diff --git a/examples/userPropagator/CMakeLists.txt b/examples/userPropagator/CMakeLists.txt index 384d257dc..0a5039a2b 100644 --- a/examples/userPropagator/CMakeLists.txt +++ b/examples/userPropagator/CMakeLists.txt @@ -15,9 +15,9 @@ find_package(Z3 ) ################################################################################ -# Z3 C++ API bindings require C++11 +# Z3 C++ API bindings require C++11, but this code needs later. ################################################################################ -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS "Z3_FOUND: ${Z3_FOUND}") diff --git a/scripts/build-win-signed.yml b/scripts/build-win-signed.yml index 1b0bb111a..f2eba1892 100644 --- a/scripts/build-win-signed.yml +++ b/scripts/build-win-signed.yml @@ -22,6 +22,22 @@ jobs: python scripts\mk_win_dist.py --${{parameters.BuildArchitecture}}-only --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk + - task: CopyFiles@2 + displayName: 'Collect Symbols' + inputs: + sourceFolder: dist + contents: '**/*.pdb' + targetFolder: '$(Build.ArtifactStagingDirectory)/symbols' + # Publish symbol archive to match nuget package + # Index your source code and publish symbols to a file share or Azure Artifacts symbol server + - task: PublishSymbols@2 + inputs: + symbolsFolder: '$(Build.ArtifactStagingDirectory)/symbols' + searchPattern: '**/*.pdb' + indexSources: false # Github sources not supported + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true - task: EsrpCodeSigning@1 displayName: Sign inputs: diff --git a/scripts/jsdoctest.yml b/scripts/jsdoctest.yml index 1a3a5286f..0556a6cf5 100644 --- a/scripts/jsdoctest.yml +++ b/scripts/jsdoctest.yml @@ -12,7 +12,7 @@ stages: - job: UbuntuDoc displayName: "Ubuntu Doc build" pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" steps: # TODO setup emscripten with no-install, then run - script: npm --prefix=src/api/js ci diff --git a/scripts/mk_genfile_common.py b/scripts/mk_genfile_common.py index f4b54cba0..3b19b8c25 100644 --- a/scripts/mk_genfile_common.py +++ b/scripts/mk_genfile_common.py @@ -886,8 +886,7 @@ def mk_hpp_from_pyg(pyg_file, output_dir): hpp = os.path.join(dirname, '%s.hpp' % class_name) out = open(hpp, 'w') out.write('// Automatically generated file\n') - out.write('#ifndef __%s_HPP_\n' % class_name.upper()) - out.write('#define __%s_HPP_\n' % class_name.upper()) + out.write('#pragma once\n') out.write('#include "util/params.h"\n') if export: out.write('#include "util/gparams.h"\n') @@ -919,7 +918,6 @@ def mk_hpp_from_pyg(pyg_file, output_dir): out.write(' %s %s() const { return p.%s("%s", %s); }\n' % (TYPE2CTYPE[param[1]], to_c_method(param[0]), TYPE2GETTER[param[1]], param[0], pyg_default_as_c_literal(param))) out.write('};\n') - out.write('#endif\n') out.close() OUTPUT_HPP_FILE.append(hpp) diff --git a/scripts/mk_nuget_task.py b/scripts/mk_nuget_task.py index 75d26a1c5..dd81349df 100644 --- a/scripts/mk_nuget_task.py +++ b/scripts/mk_nuget_task.py @@ -21,7 +21,8 @@ def mk_dir(d): if not os.path.exists(d): os.makedirs(d) -os_info = { 'ubuntu-18' : ('so', 'linux-x64'), +os_info = { 'ubuntu-latest' : ('so', 'linux-x64'), + 'ubuntu-18' : ('so', 'linux-x64'), 'ubuntu-20' : ('so', 'linux-x64'), 'glibc-2.31' : ('so', 'linux-x64'), 'x64-win' : ('dll', 'win-x64'), diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 530798cfb..65c091842 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, 11, 0, 0) + set_version(4, 11, 3, 0) # express a default build version or pick up ci build version # Z3 Project definition def init_project_def(): @@ -27,7 +27,7 @@ def init_project_def(): add_lib('params', ['util']) add_lib('smt_params', ['params'], 'smt/params') add_lib('grobner', ['ast', 'dd', 'simplex'], 'math/grobner') - add_lib('sat', ['util', 'dd', 'grobner']) + add_lib('sat', ['params', 'util', 'dd', 'grobner']) add_lib('nlsat', ['polynomial', 'sat']) add_lib('lp', ['util', 'nlsat', 'grobner', 'interval', 'smt_params'], 'math/lp') add_lib('rewriter', ['ast', 'polynomial', 'automata', 'params'], 'ast/rewriter') @@ -38,7 +38,7 @@ def init_project_def(): add_lib('substitution', ['ast', 'rewriter'], 'ast/substitution') add_lib('parser_util', ['ast'], 'parsers/util') add_lib('proofs', ['rewriter', 'util'], 'ast/proofs') - add_lib('solver', ['model', 'tactic', 'proofs']) + add_lib('solver', ['params', 'model', 'tactic', 'proofs']) add_lib('cmd_context', ['solver', 'rewriter', 'params']) add_lib('smt2parser', ['cmd_context', 'parser_util'], 'parsers/smt2') add_lib('pattern', ['normal_forms', 'smt2parser', 'rewriter'], 'ast/pattern') @@ -87,7 +87,7 @@ def init_project_def(): API_files = ['z3_api.h', 'z3_ast_containers.h', 'z3_algebraic.h', 'z3_polynomial.h', 'z3_rcf.h', 'z3_fixedpoint.h', 'z3_optimization.h', 'z3_fpa.h', 'z3_spacer.h'] add_lib('api', ['portfolio', 'realclosure', 'opt'], includes2install=['z3.h', 'z3_v1.h', 'z3_macros.h'] + API_files) - add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'qe', 'arith_tactics'], 'cmd_context/extra_cmds') + add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'qe', 'euf', 'arith_tactics'], 'cmd_context/extra_cmds') add_exe('shell', ['api', 'sat', 'extra_cmds', 'opt'], exe_name='z3') add_exe('test', ['polysat','api', 'fuzzing', 'simplex', 'sat_smt'], exe_name='test-z3', install=False) _libz3Component = add_dll('api_dll', ['api', 'sat', 'extra_cmds'], 'api/dll', diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 83171ab66..635b8cec1 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -91,6 +91,7 @@ TRACE = False PYTHON_ENABLED=False DOTNET_CORE_ENABLED=False DOTNET_KEY_FILE=getenv("Z3_DOTNET_KEY_FILE", None) +ASSEMBLY_VERSION=getenv("Z2_ASSEMBLY_VERSION", None) JAVA_ENABLED=False ML_ENABLED=False PYTHON_INSTALL_ENABLED=False @@ -540,15 +541,33 @@ def find_c_compiler(): raise MKException('C compiler was not found. Try to set the environment variable CC with the C compiler available in your system.') def set_version(major, minor, build, revision): - global VER_MAJOR, VER_MINOR, VER_BUILD, VER_TWEAK, GIT_DESCRIBE + global ASSEMBLY_VERSION, VER_MAJOR, VER_MINOR, VER_BUILD, VER_TWEAK, GIT_DESCRIBE + + # We need to give the assembly a build specific version + # global version overrides local default expression + if ASSEMBLY_VERSION is not None: + versionSplits = ASSEMBLY_VERSION.split('.') + if len(versionSplits) > 3: + VER_MAJOR = versionSplits[0] + VER_MINOR = versionSplits[1] + VER_BUILD = versionSplits[2] + VER_TWEAK = versionSplits[3] + print("Set Assembly Version (BUILD):", VER_MAJOR, VER_MINOR, VER_BUILD, VER_TWEAK) + return + + # use parameters to set up version if not provided by script args VER_MAJOR = major VER_MINOR = minor VER_BUILD = build VER_TWEAK = revision + + # update VER_TWEAK base on github if GIT_DESCRIBE: branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) VER_TWEAK = int(check_output(['git', 'rev-list', '--count', 'HEAD'])) - + + print("Set Assembly Version (DEFAULT):", VER_MAJOR, VER_MINOR, VER_BUILD, VER_TWEAK) + def get_version(): return (VER_MAJOR, VER_MINOR, VER_BUILD, VER_TWEAK) @@ -666,6 +685,7 @@ def display_help(exit_code): print(" --optimize generate optimized code during linking.") print(" --dotnet generate .NET platform bindings.") print(" --dotnet-key= sign the .NET assembly using the private key in .") + print(" --assembly-version= provide version number for build") print(" --java generate Java bindings.") print(" --ml generate OCaml bindings.") print(" --js generate JScript bindings.") @@ -700,14 +720,14 @@ def display_help(exit_code): # Parse configuration option for mk_make script def parse_options(): global VERBOSE, DEBUG_MODE, IS_WINDOWS, VS_X64, ONLY_MAKEFILES, SHOW_CPPS, VS_PROJ, TRACE, VS_PAR, VS_PAR_NUM - global DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, JAVA_ENABLED, ML_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED + global DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, ASSEMBLY_VERSION, JAVA_ENABLED, ML_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED global LINUX_X64, SLOW_OPTIMIZE, LOG_SYNC, SINGLE_THREADED global GUARD_CF, ALWAYS_DYNAMIC_BASE, IS_ARCH_ARM64 try: options, remainder = getopt.gnu_getopt(sys.argv[1:], 'b:df:sxa:hmcvtnp:gj', ['build=', 'debug', 'silent', 'x64', 'arm64=', 'help', 'makefiles', 'showcpp', 'vsproj', 'guardcf', - 'trace', 'dotnet', 'dotnet-key=', 'staticlib', 'prefix=', 'gmp', 'java', 'parallel=', 'gprof', 'js', + 'trace', 'dotnet', 'dotnet-key=', 'assembly-version=', 'staticlib', 'prefix=', 'gmp', 'java', 'parallel=', 'gprof', 'js', 'githash=', 'git-describe', 'x86', 'ml', 'optimize', 'pypkgdir=', 'python', 'staticbin', 'log-sync', 'single-threaded']) except: print("ERROR: Invalid command line option") @@ -745,6 +765,8 @@ def parse_options(): DOTNET_CORE_ENABLED = True elif opt in ('--dotnet-key'): DOTNET_KEY_FILE = arg + elif opt in ('--assembly-version'): + ASSEMBLY_VERSION = arg elif opt in ('--staticlib'): STATIC_LIB = True elif opt in ('--staticbin'): @@ -1694,7 +1716,9 @@ class DotNetDLLComponent(Component): key = "%s" % self.key_file key += "\ntrue" - version = get_version_string(3) + version = get_version_string(4) + + print("Version output to csproj:", version) core_csproj_str = """ @@ -1702,7 +1726,7 @@ class DotNetDLLComponent(Component): netstandard1.4 8.0 $(DefineConstants);DOTNET_CORE - portable + full Microsoft.Z3 Library Microsoft.Z3 @@ -2826,6 +2850,9 @@ def update_version(): minor = VER_MINOR build = VER_BUILD revision = VER_TWEAK + + print("UpdateVersion:", get_full_version_string(major, minor, build, revision)) + if major is None or minor is None or build is None or revision is None: raise MKException("set_version(major, minor, build, revision) must be used before invoking update_version()") if not ONLY_MAKEFILES: diff --git a/scripts/mk_win_dist.py b/scripts/mk_win_dist.py index 8f00b2755..b6a859e76 100644 --- a/scripts/mk_win_dist.py +++ b/scripts/mk_win_dist.py @@ -24,6 +24,7 @@ BUILD_X86_DIR=os.path.join('build-dist', 'x86') VERBOSE=True DIST_DIR='dist' FORCE_MK=False +ASSEMBLY_VERSION=None DOTNET_CORE_ENABLED=True DOTNET_KEY_FILE=None JAVA_ENABLED=True @@ -62,6 +63,7 @@ def display_help(): print(" -s, --silent do not print verbose messages.") print(" -b , --build= subdirectory where x86 and x64 Z3 versions will be built (default: build-dist).") print(" -f, --force force script to regenerate Makefiles.") + print(" --assembly-version assembly version for dll") print(" --nodotnet do not include .NET bindings in the binary distribution files.") print(" --dotnet-key= strongname sign the .NET assembly with the private key in .") print(" --nojava do not include Java bindings in the binary distribution files.") @@ -74,7 +76,7 @@ def display_help(): # Parse configuration option for mk_make script def parse_options(): - global FORCE_MK, JAVA_ENABLED, ZIP_BUILD_OUTPUTS, GIT_HASH, DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, PYTHON_ENABLED, X86ONLY, X64ONLY + global FORCE_MK, JAVA_ENABLED, ZIP_BUILD_OUTPUTS, GIT_HASH, DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, ASSEMBLY_VERSION, PYTHON_ENABLED, X86ONLY, X64ONLY path = BUILD_DIR options, remainder = getopt.gnu_getopt(sys.argv[1:], 'b:hsf', ['build=', 'help', @@ -83,6 +85,7 @@ def parse_options(): 'nojava', 'nodotnet', 'dotnet-key=', + 'assembly-version=', 'zip', 'githash', 'nopython', @@ -102,6 +105,8 @@ def parse_options(): FORCE_MK = True elif opt == '--nodotnet': DOTNET_CORE_ENABLED = False + elif opt == '--assembly-version': + ASSEMBLY_VERSION = arg elif opt == '--nopython': PYTHON_ENABLED = False elif opt == '--dotnet-key': @@ -131,8 +136,10 @@ def mk_build_dir(path, x64): opts = ["python", os.path.join('scripts', 'mk_make.py'), parallel, "-b", path] if DOTNET_CORE_ENABLED: opts.append('--dotnet') - if not DOTNET_KEY_FILE is None: + if DOTNET_KEY_FILE is not None: opts.append('--dotnet-key=' + DOTNET_KEY_FILE) + if ASSEMBLY_VERSION is not None: + opts.append('--assembly-version=' + ASSEMBLY_VERSION) if JAVA_ENABLED: opts.append('--java') if x64: @@ -196,6 +203,7 @@ def mk_z3s(): def get_z3_name(x64): major, minor, build, revision = get_version() + print("Assembly version:", major, minor, build, revision) if x64: platform = "x64" else: @@ -299,9 +307,10 @@ def cp_licenses(): cp_license(False) def init_flags(): - global DOTNET_KEY_FILE, JAVA_ENABLED, PYTHON_ENABLED + global DOTNET_KEY_FILE, JAVA_ENABLED, PYTHON_ENABLED, ASSEMBLY_VERSION mk_util.DOTNET_CORE_ENABLED = True mk_util.DOTNET_KEY_FILE = DOTNET_KEY_FILE + mk_util.ASSEMBLY_VERSION = ASSEMBLY_VERSION mk_util.JAVA_ENABLED = JAVA_ENABLED mk_util.PYTHON_ENABLED = PYTHON_ENABLED mk_util.ALWAYS_DYNAMIC_BASE = True diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index 33bed5b55..ed7c9cb12 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -1,14 +1,13 @@ variables: - Major: '4' Minor: '11' - Patch: '0' - NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) + Patch: '3' + AssemblyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId) + NightlyVersion: $(AssemblyVersion)-$(Build.DefinitionName) stages: - stage: Build jobs: - - job: Mac displayName: "Mac Build" pool: @@ -23,7 +22,6 @@ stages: artifactName: 'Mac' targetPath: $(Build.ArtifactStagingDirectory) - - job: MacArm64 displayName: "Mac ARM64 Build" pool: @@ -37,7 +35,6 @@ stages: artifactName: 'MacArm64' targetPath: $(Build.ArtifactStagingDirectory) - - job: Ubuntu displayName: "Ubuntu build" pool: @@ -55,7 +52,7 @@ stages: - job: UbuntuDoc displayName: "Ubuntu Doc build" pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" steps: - script: sudo apt-get install ocaml opam libgmp-dev - script: opam init -y @@ -76,6 +73,7 @@ stages: eval `opam config env` cd doc python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 + python mk_params_doc.py 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 .. @@ -92,7 +90,7 @@ stages: name: ManyLinux displayName: "ManyLinux build" pool: - vmImage: "Ubuntu-18.04" + vmImage: "ubuntu-latest" container: "quay.io/pypa/manylinux2010_x86_64:latest" steps: - script: $(python) scripts/mk_unix_dist.py --nodotnet --nojava @@ -111,7 +109,7 @@ stages: # name: MuslLinux # displayName: "MuslLinux build" # pool: -# vmImage: "Ubuntu-18.04" +# vmImage: "ubuntu-latest" # container: "quay.io/pypa/musllinux_1_1_x86_64:latest" # steps: # - script: $(python) scripts/mk_unix_dist.py --nodotnet --nojava @@ -123,7 +121,6 @@ stages: # artifactName: '$(name)Build' # targetPath: $(Build.ArtifactStagingDirectory) - - job: Windows32 displayName: "Windows 32-bit build" pool: @@ -134,6 +131,7 @@ stages: script: call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86 & python scripts\mk_win_dist.py + --assembly-version=$(AssemblyVersion) --x86-only --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --zip @@ -146,6 +144,22 @@ stages: inputs: targetPath: $(Build.ArtifactStagingDirectory) artifactName: 'Windows32' + - task: CopyFiles@2 + displayName: 'Collect Symbols' + inputs: + sourceFolder: dist + contents: '**/*.pdb' + targetFolder: '$(Build.ArtifactStagingDirectory)/symbols' + # Publish symbol archive to match nuget package + # Index your source code and publish symbols to a file share or Azure Artifacts symbol server + - task: PublishSymbols@2 + inputs: + symbolsFolder: '$(Build.ArtifactStagingDirectory)/symbols' + searchPattern: '**/*.pdb' + indexSources: false # Github not supported + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true - job: Windows64 displayName: "Windows 64-bit build" @@ -157,6 +171,7 @@ stages: script: call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 & python scripts\mk_win_dist.py + --assembly-version=$(AssemblyVersion) --x64-only --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --zip @@ -169,8 +184,22 @@ stages: inputs: targetPath: $(Build.ArtifactStagingDirectory) artifactName: 'Windows64' - - + - task: CopyFiles@2 + displayName: 'Collect Symbols' + inputs: + sourceFolder: dist + contents: '**/*.pdb' + targetFolder: '$(Build.ArtifactStagingDirectory)/symbols' + # Publish symbol archive to match nuget package + # Index your source code and publish symbols to a file share or Azure Artifacts symbol server + - task: PublishSymbols@2 + inputs: + symbolsFolder: '$(Build.ArtifactStagingDirectory)/symbols' + searchPattern: '**/*.pdb' + indexSources: false # Github not supported + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true - stage: Package jobs: @@ -506,7 +535,7 @@ stages: - task: GitHubRelease@0 continueOnError: true inputs: - gitHubConnection: Z3GitHub + gitHubConnection: Z3Prover repositoryName: 'Z3Prover/z3' action: 'delete' # target: '$(Build.SourceVersion)' @@ -515,7 +544,7 @@ stages: - task: GitHubRelease@0 continueOnError: true inputs: - gitHubConnection: Z3GitHub + gitHubConnection: Z3Prover repositoryName: 'Z3Prover/z3' action: 'create' # target: '$(Build.SourceVersion)' @@ -555,15 +584,16 @@ stages: displayName: 'NuGet Nightly x64 push' inputs: command: push - publishVstsFeed: 'Z3Nightly' + publishVstsFeed: 'Z3Build/Z3-Nightly-Builds' packagesToPush: $(Agent.TempDirectory)/x64/*.nupkg allowPackageConflicts: true - task: NuGetCommand@2 displayName: 'NuGet Nightly x86 push' inputs: command: push - publishVstsFeed: 'Z3Nightly' + publishVstsFeed: 'Z3Build/Z3-Nightly-Builds' packagesToPush: $(Agent.TempDirectory)/x86/*.nupkg allowPackageConflicts: true - + + # TBD: run regression tests on generated binaries. diff --git a/scripts/release.yml b/scripts/release.yml index f305e7c74..c48457c1c 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.11.0' + ReleaseVersion: '4.11.3' stages: @@ -56,8 +56,6 @@ stages: artifactName: 'MacArm64' targetPath: $(Build.ArtifactStagingDirectory) - - - job: UbuntuBuild displayName: "Ubuntu build" pool: @@ -87,11 +85,10 @@ stages: artifactName: 'UbuntuBuild' targetPath: $(Build.ArtifactStagingDirectory) - - job: UbuntuDoc displayName: "Ubuntu Doc build" pool: - vmImage: "Ubuntu-latest" + vmImage: "ubuntu-latest" steps: - script: sudo apt-get install ocaml opam libgmp-dev - script: opam init -y @@ -112,6 +109,7 @@ stages: eval `opam config env` cd doc python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 + python mk_params_doc.py 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 .. @@ -168,6 +166,7 @@ stages: ReleaseVersion: $(ReleaseVersion) BuildArchitecture: 'x86' + # Creates Z3 packages in various formats - stage: Package jobs: @@ -485,7 +484,7 @@ stages: path: $(Agent.TempDirectory) - task: GitHubRelease@0 inputs: - gitHubConnection: Z3GitHub + gitHubConnection: Z3Prover repositoryName: $(Build.Repository.Name) action: 'create' target: '$(Build.SourceVersion)' @@ -498,7 +497,7 @@ stages: isDraft: true isPreRelease: true - # Enable on release (after fixing Nuget key) + - job: NuGetPublish condition: eq(1,0) displayName: "Publish to NuGet.org" @@ -518,7 +517,6 @@ stages: nuGetFeedType: External publishFeedCredentials: $(NugetZ3) packagesToPush: $(Agent.TempDirectory)/*.nupkg - # Enable on release: - job: PyPIPublish diff --git a/scripts/update_api.py b/scripts/update_api.py index 37b8a31af..41a9face5 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -776,6 +776,13 @@ def mk_java(java_src, java_dir, package_name): java_wrapper.write(' jfieldID fid = jenv->GetFieldID(mc, "value", "I");\n') java_wrapper.write(' jenv->SetIntField(a%s, fid, (jint) _a%s);\n' % (i, i)) java_wrapper.write(' }\n') + elif param_type(param) == STRING: + java_wrapper.write(' {\n') + java_wrapper.write(' jclass mc = jenv->GetObjectClass(a%s);\n' % i) + java_wrapper.write(' jfieldID fid = jenv->GetFieldID(mc, "value", "Ljava/lang/String;");') + java_wrapper.write(' jstring fval = jenv->NewStringUTF(_a%s);\n' % i) + java_wrapper.write(' jenv->SetObjectField(a%s, fid, fval);\n' % i) + java_wrapper.write(' }\n') else: java_wrapper.write(' {\n') java_wrapper.write(' jclass mc = jenv->GetObjectClass(a%s);\n' % i) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6a9a9879..ec70af7e1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,27 @@ if (Z3_BUILD_LIBZ3_SHARED) else() set(lib_type "STATIC") endif() +# Enable static msvc runtime. +if (MSVC AND Z3_BUILD_LIBZ3_MSVC_STATIC) + set(CompilerFlags + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELWITHDEBINFO + ) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + string(REPLACE "/MDd" "/MTd" ${CompilerFlag} "${${CompilerFlag}}") + set(${CompilerFlag} "${${CompilerFlag}}" CACHE STRING "msvc compiler flags" FORCE) + message("MSVC flags: ${CompilerFlag}:${${CompilerFlag}}") + endforeach() +endif(MSVC) add_library(libz3 ${lib_type} ${object_files}) target_include_directories(libz3 INTERFACE $ diff --git a/src/api/api_arith.cpp b/src/api/api_arith.cpp index 57f96dd6d..9671dbc26 100644 --- a/src/api/api_arith.cpp +++ b/src/api/api_arith.cpp @@ -22,6 +22,8 @@ Revision History: #include "ast/arith_decl_plugin.h" #include "math/polynomial/algebraic_numbers.h" +#include + #define MK_ARITH_OP(NAME, OP) MK_NARY(NAME, mk_c(c)->get_arith_fid(), OP, SKIP) #define MK_BINARY_ARITH_OP(NAME, OP) MK_BINARY(NAME, mk_c(c)->get_arith_fid(), OP, SKIP) #define MK_ARITH_PRED(NAME, OP) MK_BINARY(NAME, mk_c(c)->get_arith_fid(), OP, SKIP) @@ -88,7 +90,25 @@ extern "C" { MK_ARITH_PRED(Z3_mk_gt, OP_GT); MK_ARITH_PRED(Z3_mk_le, OP_LE); MK_ARITH_PRED(Z3_mk_ge, OP_GE); - MK_ARITH_PRED(Z3_mk_divides, OP_IDIVIDES); + + Z3_ast Z3_API Z3_mk_divides(Z3_context c, Z3_ast n1, Z3_ast n2) { + Z3_TRY; + LOG_Z3_mk_divides(c, n1, n2); + RESET_ERROR_CODE(); + rational val; + if (!is_expr(n1) || !mk_c(c)->autil().is_numeral(to_expr(n1), val) || !val.is_unsigned()) { + SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); + RETURN_Z3(nullptr); + } + parameter p(val.get_unsigned()); + expr* arg = to_expr(n2); + expr* a = mk_c(c)->m().mk_app(mk_c(c)->get_arith_fid(), OP_IDIVIDES, 1, &p, 1, &arg); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(nullptr); + } + MK_UNARY(Z3_mk_int2real, mk_c(c)->get_arith_fid(), OP_TO_REAL, SKIP); MK_UNARY(Z3_mk_real2int, mk_c(c)->get_arith_fid(), OP_TO_INT, SKIP); MK_UNARY(Z3_mk_is_int, mk_c(c)->get_arith_fid(), OP_IS_INT, SKIP); diff --git a/src/api/api_array.cpp b/src/api/api_array.cpp index 7aa3a87bf..cb254dbdd 100644 --- a/src/api/api_array.cpp +++ b/src/api/api_array.cpp @@ -294,14 +294,19 @@ extern "C" { return Z3_mk_store(c, set, elem, Z3_mk_false(c)); } + static bool is_array_sort(Z3_context c, Z3_sort t) { + return + to_sort(t)->get_family_id() == mk_c(c)->get_array_fid() && + to_sort(t)->get_decl_kind() == ARRAY_SORT; + } + Z3_sort Z3_API Z3_get_array_sort_domain(Z3_context c, Z3_sort t) { Z3_TRY; LOG_Z3_get_array_sort_domain(c, t); RESET_ERROR_CODE(); CHECK_VALID_AST(t, nullptr); - if (to_sort(t)->get_family_id() == mk_c(c)->get_array_fid() && - to_sort(t)->get_decl_kind() == ARRAY_SORT) { - Z3_sort r = reinterpret_cast(to_sort(t)->get_parameter(0).get_ast()); + if (is_array_sort(c, t)) { + Z3_sort r = of_sort(get_array_domain(to_sort(t), 0)); RETURN_Z3(r); } SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); @@ -314,10 +319,8 @@ extern "C" { LOG_Z3_get_array_sort_domain_n(c, t, idx); RESET_ERROR_CODE(); CHECK_VALID_AST(t, nullptr); - if (to_sort(t)->get_family_id() == mk_c(c)->get_array_fid() && - to_sort(t)->get_decl_kind() == ARRAY_SORT && - get_array_arity(to_sort(t)) > idx) { - Z3_sort r = reinterpret_cast(get_array_domain(to_sort(t), idx)); + if (is_array_sort(c, t) && get_array_arity(to_sort(t)) > idx) { + Z3_sort r = of_sort(get_array_domain(to_sort(t), idx)); RETURN_Z3(r); } SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); @@ -330,10 +333,8 @@ extern "C" { LOG_Z3_get_array_sort_range(c, t); RESET_ERROR_CODE(); CHECK_VALID_AST(t, nullptr); - if (to_sort(t)->get_family_id() == mk_c(c)->get_array_fid() && - to_sort(t)->get_decl_kind() == ARRAY_SORT) { - unsigned n = to_sort(t)->get_num_parameters(); - Z3_sort r = reinterpret_cast(to_sort(t)->get_parameter(n-1).get_ast()); + if (is_array_sort(c, t)) { + Z3_sort r = of_sort(get_array_range(to_sort(t))); RETURN_Z3(r); } SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index 647cc8631..df3059d4b 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -1178,7 +1178,7 @@ extern "C" { case OP_BSMOD: return Z3_OP_BSMOD; case OP_BSDIV0: return Z3_OP_BSDIV0; case OP_BUDIV0: return Z3_OP_BUDIV0; - case OP_BSREM0: return Z3_OP_BUREM0; + case OP_BSREM0: return Z3_OP_BSREM0; case OP_BUREM0: return Z3_OP_BUREM0; case OP_BSMOD0: return Z3_OP_BSMOD0; case OP_ULEQ: return Z3_OP_ULEQ; diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index a16341955..6d9af74a0 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -153,7 +153,7 @@ namespace api { 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());); + DEBUG_CODE(if (!m_concurrent_dec_ref) warning_msg("Uncollected memory: %d: %s", kv.m_key, typeid(*val).name());); dealloc(val); } if (m_params.owns_manager()) diff --git a/src/api/api_parsers.cpp b/src/api/api_parsers.cpp index 3a1fe7af1..31ae28f47 100644 --- a/src/api/api_parsers.cpp +++ b/src/api/api_parsers.cpp @@ -124,7 +124,7 @@ extern "C" { Z3_CATCH; } - Z3_ast_vector Z3_parser_context_parse_stream(Z3_context c, scoped_ptr& ctx, bool owned, std::istream& is) { + static 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); @@ -163,6 +163,7 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } + static Z3_ast_vector parse_smtlib2_stream(bool exec, Z3_context c, std::istream& is, unsigned num_sorts, Z3_symbol const _sort_names[], @@ -242,6 +243,7 @@ extern "C" { std::istringstream is(s); ctx->set_regular_stream(ous); ctx->set_diagnostic_stream(ous); + cmd_context::scoped_redirect _redirect(*ctx); try { if (!parse_smt2_commands(*ctx.get(), is)) { SET_ERROR_CODE(Z3_PARSER_ERROR, ous.str()); @@ -256,6 +258,4 @@ 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 bf8a52d51..a56ca3d3c 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -36,7 +36,7 @@ Revision History: #include "smt/smt_implied_equalities.h" #include "solver/smt_logics.h" #include "solver/tactic2solver.h" -#include "solver/solver_params.hpp" +#include "params/solver_params.hpp" #include "cmd_context/cmd_context.h" #include "parsers/smt2/smt2parser.h" #include "sat/dimacs.h" diff --git a/src/api/api_stats.cpp b/src/api/api_stats.cpp index c90850404..a3b8bacf0 100644 --- a/src/api/api_stats.cpp +++ b/src/api/api_stats.cpp @@ -28,7 +28,7 @@ extern "C" { RESET_ERROR_CODE(); std::ostringstream buffer; to_stats_ref(s).display_smt2(buffer); - std::string result = buffer.str(); + std::string result = std::move(buffer).str(); // Hack for removing the trailing '\n' SASSERT(result.size() > 0); result.resize(result.size()-1); diff --git a/src/api/api_util.h b/src/api/api_util.h index 80f4f5ec7..0ff2c8ddd 100644 --- a/src/api/api_util.h +++ b/src/api/api_util.h @@ -40,7 +40,7 @@ namespace api { context& m_context; public: object(context& c); - virtual ~object() {} + virtual ~object() = default; unsigned ref_count() const { return m_ref_count; } unsigned id() const { return m_id; } void inc_ref(); diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index 2731d6d27..0275a22e1 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -23,7 +23,6 @@ Notes: #include #include #include -#include #include #include #include @@ -88,7 +87,7 @@ namespace z3 { class exception : public std::exception { std::string m_msg; public: - virtual ~exception() throw() {} + virtual ~exception() throw() = default; exception(char const * msg):m_msg(msg) {} char const * msg() const { return m_msg.c_str(); } char const * what() const throw() { return m_msg.c_str(); } @@ -4315,6 +4314,16 @@ namespace z3 { Z3_solver_propagate_consequence(ctx(), cb, fixed.size(), _fixed.ptr(), 0, nullptr, nullptr, conseq); } + void conflict(expr_vector const& fixed, expr_vector const& lhs, expr_vector const& rhs) { + assert(cb); + assert(lhs.size() == rhs.size()); + expr conseq = ctx().bool_val(false); + array _fixed(fixed); + array _lhs(lhs); + array _rhs(rhs); + Z3_solver_propagate_consequence(ctx(), cb, fixed.size(), _fixed.ptr(), lhs.size(), _lhs.ptr(), _rhs.ptr(), conseq); + } + void propagate(expr_vector const& fixed, expr const& conseq) { assert(cb); assert((Z3_context)conseq.ctx() == (Z3_context)ctx()); diff --git a/src/api/dotnet/AST.cs b/src/api/dotnet/AST.cs index 0afff2c42..48a7175eb 100644 --- a/src/api/dotnet/AST.cs +++ b/src/api/dotnet/AST.cs @@ -208,37 +208,22 @@ namespace Microsoft.Z3 internal AST(Context ctx) : base(ctx) { Debug.Assert(ctx != null); } internal AST(Context ctx, IntPtr obj) : base(ctx, obj) { Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - // Console.WriteLine("AST IncRef()"); - if (Context == null || o == IntPtr.Zero) - return; - Context.AST_DRQ.IncAndClear(Context, o); - base.IncRef(o); + if (Context != null && o != IntPtr.Zero) + Native.Z3_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - // Console.WriteLine("AST DecRef()"); - if (Context == null || o == IntPtr.Zero) - return; - Context.AST_DRQ.Add(o); - base.DecRef(o); + if (Context != null && o != IntPtr.Zero) + { + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_dec_ref(Context.nCtx, o); + } + } } internal static AST Create(Context ctx, IntPtr obj) diff --git a/src/api/dotnet/ASTMap.cs b/src/api/dotnet/ASTMap.cs index 0dde04411..ad3c47106 100644 --- a/src/api/dotnet/ASTMap.cs +++ b/src/api/dotnet/ASTMap.cs @@ -125,31 +125,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_ast_map_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_ast_map_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.ASTMap_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_ast_map_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.ASTMap_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_ast_map_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/ASTVector.cs b/src/api/dotnet/ASTVector.cs index fcfa6bd65..777d7d56c 100644 --- a/src/api/dotnet/ASTVector.cs +++ b/src/api/dotnet/ASTVector.cs @@ -233,31 +233,19 @@ namespace Microsoft.Z3 internal ASTVector(Context ctx, IntPtr obj) : base(ctx, obj) { Debug.Assert(ctx != null); } internal ASTVector(Context ctx) : base(ctx, Native.Z3_mk_ast_vector(ctx.nCtx)) { Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_ast_vector_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_ast_vector_dec_ref(ctx.nCtx, obj); - } - }; internal override void IncRef(IntPtr o) { - Context.ASTVector_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_ast_vector_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.ASTVector_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_ast_vector_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/ApplyResult.cs b/src/api/dotnet/ApplyResult.cs index 342bf3216..728fde3d5 100644 --- a/src/api/dotnet/ApplyResult.cs +++ b/src/api/dotnet/ApplyResult.cs @@ -67,31 +67,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_apply_result_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_apply_result_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.ApplyResult_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_apply_result_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.ApplyResult_DRQ.Add(o); - base.DecRef(o); + lock(Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_apply_result_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/CMakeLists.txt b/src/api/dotnet/CMakeLists.txt index 82abfbf09..b2ba590ce 100644 --- a/src/api/dotnet/CMakeLists.txt +++ b/src/api/dotnet/CMakeLists.txt @@ -59,7 +59,6 @@ set(Z3_DOTNET_ASSEMBLY_SOURCES_IN_SRC_TREE Context.cs DatatypeExpr.cs DatatypeSort.cs - Deprecated.cs EnumSort.cs Expr.cs FiniteDomainExpr.cs @@ -76,7 +75,6 @@ set(Z3_DOTNET_ASSEMBLY_SOURCES_IN_SRC_TREE FuncInterp.cs Global.cs Goal.cs - IDecRefQueue.cs IntExpr.cs IntNum.cs IntSort.cs diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs index 8896904dd..80b5a95f1 100644 --- a/src/api/dotnet/Context.cs +++ b/src/api/dotnet/Context.cs @@ -42,6 +42,7 @@ namespace Microsoft.Z3 lock (creation_lock) { m_ctx = Native.Z3_mk_context_rc(IntPtr.Zero); + Native.Z3_enable_concurrent_dec_ref(m_ctx); InitContext(); } } @@ -75,6 +76,7 @@ namespace Microsoft.Z3 foreach (KeyValuePair kv in settings) Native.Z3_set_param_value(cfg, kv.Key, kv.Value); m_ctx = Native.Z3_mk_context_rc(cfg); + Native.Z3_enable_concurrent_dec_ref(m_ctx); Native.Z3_del_config(cfg); InitContext(); } @@ -124,7 +126,7 @@ namespace Microsoft.Z3 /// internal Symbol[] MkSymbols(string[] names) { - if (names == null) return null; + if (names == null) return new Symbol[0]; Symbol[] result = new Symbol[names.Length]; for (int i = 0; i < names.Length; ++i) result[i] = MkSymbol(names[i]); return result; @@ -4793,7 +4795,6 @@ namespace Microsoft.Z3 PrintMode = Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT; m_n_err_handler = new Native.Z3_error_handler(NativeErrorHandler); // keep reference so it doesn't get collected. Native.Z3_set_error_handler(m_ctx, m_n_err_handler); - GC.SuppressFinalize(this); } internal void CheckContextMatch(Z3Object other) @@ -4852,123 +4853,9 @@ namespace Microsoft.Z3 private void ObjectInvariant() { - Debug.Assert(m_AST_DRQ != null); - Debug.Assert(m_ASTMap_DRQ != null); - Debug.Assert(m_ASTVector_DRQ != null); - Debug.Assert(m_ApplyResult_DRQ != null); - Debug.Assert(m_FuncEntry_DRQ != null); - Debug.Assert(m_FuncInterp_DRQ != null); - Debug.Assert(m_Goal_DRQ != null); - Debug.Assert(m_Model_DRQ != null); - Debug.Assert(m_Params_DRQ != null); - Debug.Assert(m_ParamDescrs_DRQ != null); - Debug.Assert(m_Probe_DRQ != null); - Debug.Assert(m_Solver_DRQ != null); - Debug.Assert(m_Statistics_DRQ != null); - Debug.Assert(m_Tactic_DRQ != null); - Debug.Assert(m_Fixedpoint_DRQ != null); - Debug.Assert(m_Optimize_DRQ != null); + // none } - readonly private AST.DecRefQueue m_AST_DRQ = new AST.DecRefQueue(); - readonly private ASTMap.DecRefQueue m_ASTMap_DRQ = new ASTMap.DecRefQueue(10); - readonly private ASTVector.DecRefQueue m_ASTVector_DRQ = new ASTVector.DecRefQueue(10); - readonly private ApplyResult.DecRefQueue m_ApplyResult_DRQ = new ApplyResult.DecRefQueue(10); - readonly private FuncInterp.Entry.DecRefQueue m_FuncEntry_DRQ = new FuncInterp.Entry.DecRefQueue(10); - readonly private FuncInterp.DecRefQueue m_FuncInterp_DRQ = new FuncInterp.DecRefQueue(10); - readonly private Goal.DecRefQueue m_Goal_DRQ = new Goal.DecRefQueue(10); - readonly private Model.DecRefQueue m_Model_DRQ = new Model.DecRefQueue(10); - readonly private Params.DecRefQueue m_Params_DRQ = new Params.DecRefQueue(10); - readonly private ParamDescrs.DecRefQueue m_ParamDescrs_DRQ = new ParamDescrs.DecRefQueue(10); - readonly private Probe.DecRefQueue m_Probe_DRQ = new Probe.DecRefQueue(10); - readonly private Solver.DecRefQueue m_Solver_DRQ = new Solver.DecRefQueue(10); - readonly private Statistics.DecRefQueue m_Statistics_DRQ = new Statistics.DecRefQueue(10); - readonly private Tactic.DecRefQueue m_Tactic_DRQ = new Tactic.DecRefQueue(10); - readonly private Fixedpoint.DecRefQueue m_Fixedpoint_DRQ = new Fixedpoint.DecRefQueue(10); - readonly private Optimize.DecRefQueue m_Optimize_DRQ = new Optimize.DecRefQueue(10); - - /// - /// AST DRQ - /// - public IDecRefQueue AST_DRQ { get { return m_AST_DRQ; } } - - /// - /// ASTMap DRQ - /// - public IDecRefQueue ASTMap_DRQ { get { return m_ASTMap_DRQ; } } - - /// - /// ASTVector DRQ - /// - public IDecRefQueue ASTVector_DRQ { get { return m_ASTVector_DRQ; } } - - /// - /// ApplyResult DRQ - /// - public IDecRefQueue ApplyResult_DRQ { get { return m_ApplyResult_DRQ; } } - - /// - /// FuncEntry DRQ - /// - public IDecRefQueue FuncEntry_DRQ { get { return m_FuncEntry_DRQ; } } - - /// - /// FuncInterp DRQ - /// - public IDecRefQueue FuncInterp_DRQ { get { return m_FuncInterp_DRQ; } } - - /// - /// Goal DRQ - /// - public IDecRefQueue Goal_DRQ { get { return m_Goal_DRQ; } } - - /// - /// Model DRQ - /// - public IDecRefQueue Model_DRQ { get { return m_Model_DRQ; } } - - /// - /// Params DRQ - /// - public IDecRefQueue Params_DRQ { get { return m_Params_DRQ; } } - - /// - /// ParamDescrs DRQ - /// - public IDecRefQueue ParamDescrs_DRQ { get { return m_ParamDescrs_DRQ; } } - - /// - /// Probe DRQ - /// - public IDecRefQueue Probe_DRQ { get { return m_Probe_DRQ; } } - - /// - /// Solver DRQ - /// - public IDecRefQueue Solver_DRQ { get { return m_Solver_DRQ; } } - - /// - /// Statistics DRQ - /// - public IDecRefQueue Statistics_DRQ { get { return m_Statistics_DRQ; } } - - /// - /// Tactic DRQ - /// - public IDecRefQueue Tactic_DRQ { get { return m_Tactic_DRQ; } } - - /// - /// FixedPoint DRQ - /// - public IDecRefQueue Fixedpoint_DRQ { get { return m_Fixedpoint_DRQ; } } - - /// - /// Optimize DRQ - /// - public IDecRefQueue Optimize_DRQ { get { return m_Fixedpoint_DRQ; } } - - internal long refCount = 0; - /// /// Finalizer. /// @@ -4984,22 +4871,6 @@ namespace Microsoft.Z3 public void Dispose() { // Console.WriteLine("Context Dispose from " + System.Threading.Thread.CurrentThread.ManagedThreadId); - AST_DRQ.Clear(this); - ASTMap_DRQ.Clear(this); - ASTVector_DRQ.Clear(this); - ApplyResult_DRQ.Clear(this); - FuncEntry_DRQ.Clear(this); - FuncInterp_DRQ.Clear(this); - Goal_DRQ.Clear(this); - Model_DRQ.Clear(this); - Params_DRQ.Clear(this); - ParamDescrs_DRQ.Clear(this); - Probe_DRQ.Clear(this); - Solver_DRQ.Clear(this); - Statistics_DRQ.Clear(this); - Tactic_DRQ.Clear(this); - Fixedpoint_DRQ.Clear(this); - Optimize_DRQ.Clear(this); if (m_boolSort != null) m_boolSort.Dispose(); if (m_intSort != null) m_intSort.Dispose(); @@ -5011,16 +4882,19 @@ namespace Microsoft.Z3 m_realSort = null; m_stringSort = null; m_charSort = null; - if (refCount == 0 && m_ctx != IntPtr.Zero) + if (m_ctx != IntPtr.Zero) { - m_n_err_handler = null; IntPtr ctx = m_ctx; - m_ctx = IntPtr.Zero; - if (!is_external) - Native.Z3_del_context(ctx); + lock (this) + { + m_n_err_handler = null; + m_ctx = IntPtr.Zero; + } + if (!is_external) + Native.Z3_del_context(ctx); } - else - GC.ReRegisterForFinalize(this); + + GC.SuppressFinalize(this); } diff --git a/src/api/dotnet/Deprecated.cs b/src/api/dotnet/Deprecated.cs deleted file mode 100644 index 64255cea2..000000000 --- a/src/api/dotnet/Deprecated.cs +++ /dev/null @@ -1,35 +0,0 @@ -/*++ -Copyright (c) 2012 Microsoft Corporation - -Module Name: - - Deprecated.cs - -Abstract: - - Expose deprecated features for use from the managed API - those who use them for experiments. - -Author: - - Christoph Wintersteiger (cwinter) 2012-03-15 - -Notes: - ---*/ -using System.Diagnostics; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Microsoft.Z3 -{ - /// - /// The main interaction with Z3 happens via the Context. - /// - public class Deprecated - { - - - } -} \ No newline at end of file diff --git a/src/api/dotnet/Fixedpoint.cs b/src/api/dotnet/Fixedpoint.cs index 15560d829..2edae1e2a 100644 --- a/src/api/dotnet/Fixedpoint.cs +++ b/src/api/dotnet/Fixedpoint.cs @@ -318,31 +318,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_fixedpoint_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_fixedpoint_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Fixedpoint_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_fixedpoint_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Fixedpoint_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_fixedpoint_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/FuncInterp.cs b/src/api/dotnet/FuncInterp.cs index 6924049d3..fec6eab95 100644 --- a/src/api/dotnet/FuncInterp.cs +++ b/src/api/dotnet/FuncInterp.cs @@ -85,31 +85,18 @@ namespace Microsoft.Z3 #region Internal internal Entry(Context ctx, IntPtr obj) : base(ctx, obj) { Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_func_entry_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_func_entry_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.FuncEntry_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_func_entry_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.FuncEntry_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_func_entry_dec_ref(Context.nCtx, o); + } } #endregion }; @@ -190,31 +177,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_func_interp_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_func_interp_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.FuncInterp_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_func_interp_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.FuncInterp_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_func_interp_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Goal.cs b/src/api/dotnet/Goal.cs index c096c0755..8183c7a79 100644 --- a/src/api/dotnet/Goal.cs +++ b/src/api/dotnet/Goal.cs @@ -252,33 +252,20 @@ namespace Microsoft.Z3 : base(ctx, Native.Z3_mk_goal(ctx.nCtx, (byte)(models ? 1 : 0), (byte)(unsatCores ? 1 : 0), (byte)(proofs ? 1 : 0))) { Debug.Assert(ctx != null); - } - - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_goal_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_goal_dec_ref(ctx.nCtx, obj); - } - }; + } internal override void IncRef(IntPtr o) { - Context.Goal_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_goal_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Goal_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_goal_dec_ref(Context.nCtx, o); + } } #endregion diff --git a/src/api/dotnet/IDecRefQueue.cs b/src/api/dotnet/IDecRefQueue.cs deleted file mode 100644 index 8af0973d6..000000000 --- a/src/api/dotnet/IDecRefQueue.cs +++ /dev/null @@ -1,103 +0,0 @@ -/*++ -Copyright (c) 2012 Microsoft Corporation - -Module Name: - - DecRefQueue.cs - -Abstract: - - Z3 Managed API: DecRef Queues - -Author: - - Christoph Wintersteiger (cwinter) 2012-03-16 - -Notes: - ---*/ - -using System.Diagnostics; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; - -namespace Microsoft.Z3 -{ - /// - /// DecRefQueue interface - /// - public abstract class IDecRefQueue - { - #region Object invariant - - private void ObjectInvariant() - { - Debug.Assert(this.m_queue != null); - } - - #endregion - - readonly private Object m_lock = new Object(); - readonly private List m_queue = new List(); - private uint m_move_limit; - - internal IDecRefQueue(uint move_limit = 1024) - { - m_move_limit = move_limit; - } - - /// - /// Sets the limit on numbers of objects that are kept back at GC collection. - /// - /// - public void SetLimit(uint l) { m_move_limit = l; } - - internal abstract void IncRef(Context ctx, IntPtr obj); - internal abstract void DecRef(Context ctx, IntPtr obj); - - internal void IncAndClear(Context ctx, IntPtr o) - { - Debug.Assert(ctx != null); - - IncRef(ctx, o); - if (m_queue.Count >= m_move_limit) Clear(ctx); - } - - internal void Add(IntPtr o) - { - if (o == IntPtr.Zero) return; - - lock (m_lock) - { - m_queue.Add(o); - } - } - - internal void Clear(Context ctx) - { - Debug.Assert(ctx != null); - - lock (m_lock) - { - foreach (IntPtr o in m_queue) - DecRef(ctx, o); - m_queue.Clear(); - } - } - } - - abstract class DecRefQueueContracts : IDecRefQueue - { - internal override void IncRef(Context ctx, IntPtr obj) - { - Debug.Assert(ctx != null); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Debug.Assert(ctx != null); - } - } -} diff --git a/src/api/dotnet/Model.cs b/src/api/dotnet/Model.cs index c35c0a727..343b2beee 100644 --- a/src/api/dotnet/Model.cs +++ b/src/api/dotnet/Model.cs @@ -302,33 +302,20 @@ namespace Microsoft.Z3 : base(ctx, obj) { Debug.Assert(ctx != null); - } - - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_model_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_model_dec_ref(ctx.nCtx, obj); - } - }; + } internal override void IncRef(IntPtr o) { - Context.Model_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_model_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Model_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_model_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Optimize.cs b/src/api/dotnet/Optimize.cs index ecd5e8e82..0694faabe 100644 --- a/src/api/dotnet/Optimize.cs +++ b/src/api/dotnet/Optimize.cs @@ -440,31 +440,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_optimize_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_optimize_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Optimize_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_optimize_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Optimize_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_optimize_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/ParamDescrs.cs b/src/api/dotnet/ParamDescrs.cs index fbfb9cd16..1975037e3 100644 --- a/src/api/dotnet/ParamDescrs.cs +++ b/src/api/dotnet/ParamDescrs.cs @@ -91,33 +91,20 @@ namespace Microsoft.Z3 : base(ctx, obj) { Debug.Assert(ctx != null); - } - - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_param_descrs_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_param_descrs_dec_ref(ctx.nCtx, obj); - } - }; + } internal override void IncRef(IntPtr o) { - Context.ParamDescrs_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_param_descrs_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.ParamDescrs_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_param_descrs_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Params.cs b/src/api/dotnet/Params.cs index e5926934a..64dd34968 100644 --- a/src/api/dotnet/Params.cs +++ b/src/api/dotnet/Params.cs @@ -147,33 +147,20 @@ namespace Microsoft.Z3 : base(ctx, Native.Z3_mk_params(ctx.nCtx)) { Debug.Assert(ctx != null); - } - - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_params_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_params_dec_ref(ctx.nCtx, obj); - } - }; + } internal override void IncRef(IntPtr o) { - Context.Params_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_params_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Params_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_params_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Probe.cs b/src/api/dotnet/Probe.cs index 5cdee79a2..e0457c2fe 100644 --- a/src/api/dotnet/Probe.cs +++ b/src/api/dotnet/Probe.cs @@ -70,31 +70,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_probe_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_probe_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Probe_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_probe_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Probe_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_probe_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Solver.cs b/src/api/dotnet/Solver.cs index 52f2ad993..00b5117ea 100644 --- a/src/api/dotnet/Solver.cs +++ b/src/api/dotnet/Solver.cs @@ -538,31 +538,18 @@ namespace Microsoft.Z3 this.BacktrackLevel = uint.MaxValue; } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_solver_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_solver_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Solver_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_solver_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Solver_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_solver_dec_ref(Context.nCtx, o); + } } private Status lboolToStatus(Z3_lbool r) diff --git a/src/api/dotnet/Statistics.cs b/src/api/dotnet/Statistics.cs index f6d72e0c7..1811951a2 100644 --- a/src/api/dotnet/Statistics.cs +++ b/src/api/dotnet/Statistics.cs @@ -19,8 +19,6 @@ Notes: using System; using System.Diagnostics; - - namespace Microsoft.Z3 { @@ -189,31 +187,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_stats_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_stats_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Statistics_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_stats_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Statistics_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_stats_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/Tactic.cs b/src/api/dotnet/Tactic.cs index 96b6da170..d87b8bc48 100644 --- a/src/api/dotnet/Tactic.cs +++ b/src/api/dotnet/Tactic.cs @@ -107,34 +107,18 @@ namespace Microsoft.Z3 Debug.Assert(ctx != null); } - /// - /// DecRefQueue - /// - internal class DecRefQueue : IDecRefQueue - { - public DecRefQueue() : base() { } - public DecRefQueue(uint move_limit) : base(move_limit) { } - internal override void IncRef(Context ctx, IntPtr obj) - { - Native.Z3_tactic_inc_ref(ctx.nCtx, obj); - } - - internal override void DecRef(Context ctx, IntPtr obj) - { - Native.Z3_tactic_dec_ref(ctx.nCtx, obj); - } - }; - internal override void IncRef(IntPtr o) { - Context.Tactic_DRQ.IncAndClear(Context, o); - base.IncRef(o); + Native.Z3_tactic_inc_ref(Context.nCtx, o); } internal override void DecRef(IntPtr o) { - Context.Tactic_DRQ.Add(o); - base.DecRef(o); + lock (Context) + { + if (Context.nCtx != IntPtr.Zero) + Native.Z3_tactic_dec_ref(Context.nCtx, o); + } } #endregion } diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index 3e9344556..b9cd4dc39 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -37,7 +37,7 @@ namespace Microsoft.Z3 /// /// Propagator context for .Net /// - public class UserPropagator + public class UserPropagator : IDisposable { /// /// Delegate type for fixed callback @@ -71,6 +71,7 @@ namespace Microsoft.Z3 Solver solver; Context ctx; Z3_solver_callback callback = IntPtr.Zero; + int callbackNesting = 0; FixedEh fixed_eh; Action final_eh; EqEh eq_eh; @@ -91,6 +92,7 @@ namespace Microsoft.Z3 void Callback(Action fn, Z3_solver_callback cb) { + this.callbackNesting++; this.callback = cb; try { @@ -102,7 +104,9 @@ namespace Microsoft.Z3 } finally { - this.callback = IntPtr.Zero; + callbackNesting--; + if (callbackNesting == 0) // callbacks can be nested (e.g., internalizing new element in "created") + this.callback = IntPtr.Zero; } } @@ -201,10 +205,20 @@ namespace Microsoft.Z3 } /// - /// Release provate memory. + /// Release private memory. /// ~UserPropagator() { + Dispose(); + } + + /// + /// Must be called. The object will not be garbage collected automatically even if the context is disposed + /// + public virtual void Dispose() + { + if (!gch.IsAllocated) + return; gch.Free(); if (solver == null) ctx.Dispose(); diff --git a/src/api/dotnet/Z3Object.cs b/src/api/dotnet/Z3Object.cs index 432885b66..4295ef054 100644 --- a/src/api/dotnet/Z3Object.cs +++ b/src/api/dotnet/Z3Object.cs @@ -50,12 +50,7 @@ namespace Microsoft.Z3 m_n_obj = IntPtr.Zero; } - if (m_ctx != null) - { - if (Interlocked.Decrement(ref m_ctx.refCount) == 0) - GC.ReRegisterForFinalize(m_ctx); - m_ctx = null; - } + m_ctx = null; GC.SuppressFinalize(this); } @@ -76,19 +71,15 @@ namespace Microsoft.Z3 internal Z3Object(Context ctx) { Debug.Assert(ctx != null); - - Interlocked.Increment(ref ctx.refCount); m_ctx = ctx; } internal Z3Object(Context ctx, IntPtr obj) { Debug.Assert(ctx != null); - - Interlocked.Increment(ref ctx.refCount); m_ctx = ctx; - IncRef(obj); m_n_obj = obj; + IncRef(obj); } internal virtual void IncRef(IntPtr o) { } @@ -109,7 +100,7 @@ namespace Microsoft.Z3 internal static IntPtr GetNativeObject(Z3Object s) { - if (s == null) return new IntPtr(); + if (s == null) return IntPtr.Zero; return s.NativeObject; } diff --git a/src/api/java/ArraySort.java b/src/api/java/ArraySort.java index 30f4178fa..3d3ef3041 100644 --- a/src/api/java/ArraySort.java +++ b/src/api/java/ArraySort.java @@ -35,6 +35,18 @@ public class ArraySort extends Sort Native.getArraySortDomain(getContext().nCtx(), getNativeObject())); } + /** + * The domain of a multi-dimensional array sort. + * @throws Z3Exception + * @throws Z3Exception on error + * @return a sort + **/ + public D getDomain(int idx) + { + return (D) Sort.create(getContext(), + Native.getArraySortDomainN(getContext().nCtx(), getNativeObject(), idx)); + } + /** * The range of the array sort. * @throws Z3Exception diff --git a/src/api/java/Context.java b/src/api/java/Context.java index 4582439ec..bb3f6fe8e 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -110,7 +110,7 @@ public class Context implements AutoCloseable { Symbol[] mkSymbols(String[] names) { if (names == null) - return null; + return new Symbol[0]; Symbol[] result = new Symbol[names.length]; for (int i = 0; i < names.length; ++i) result[i] = mkSymbol(names[i]); 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 9ec7d6eca..7fb20e627 100644 --- a/src/api/js/src/high-level/high-level.test.ts +++ b/src/api/js/src/high-level/high-level.test.ts @@ -8,7 +8,7 @@ import { Arith, Bool, Model, Z3AssertionError, Z3HighLevel } from './types'; * * **NOTE**: The set of solutions might be infinite. * Always ensure to limit amount generated, either by knowing that the - * solution space is constrainted, or by taking only a specified + * solution space is constrained, or by taking only a specified * amount of solutions * ```typescript * import { sliceAsync } from 'iter-tools'; @@ -46,7 +46,7 @@ async function* allSolutions(...assertions: Bool[]): .filter(decl => decl.arity() === 0) .map(decl => { const term = decl.call(); - // TODO(ritave): Assert not an array / uinterpeted sort + // TODO(ritave): Assert not an array / uninterpreted sort const value = model.eval(term, true); return term.neq(value); }), @@ -109,6 +109,16 @@ describe('high-level', () => { expect(await solver.check()).toStrictEqual('unsat'); }); + it('test loading a solver state from a string', async () => { + const { Solver, Not, Int } = api.Context('main'); + const solver = new Solver(); + solver.fromString("(declare-const x Int) (assert (and (< x 2) (> x 0)))") + expect(await solver.check()).toStrictEqual('sat') + const x = Int.const('x') + solver.add(Not(x.eq(1))) + expect(await solver.check()).toStrictEqual('unsat') + }); + it('disproves x = y implies g(g(x)) = g(y)', async () => { const { Solver, Int, Function, Implies, Not } = api.Context('main'); const solver = new Solver(); diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index a5e27429b..e1a2b35ab 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -224,6 +224,12 @@ export function createApi(Z3: Z3Core): Z3HighLevel { 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) { + if (Z3.is_quantifier_forall(contextPtr, ast)) + return new BoolImpl(ast); + if (Z3.is_quantifier_exists(contextPtr, ast)) + return new BoolImpl(ast); + if (Z3.is_lambda(contextPtr, ast)) + return new ExprImpl(ast); assert(false); } const sortKind = check(Z3.get_sort_kind(contextPtr, Z3.get_sort(contextPtr, ast))); @@ -1012,6 +1018,11 @@ export function createApi(Z3: Z3Core): Z3HighLevel { toString() { return check(Z3.solver_to_string(contextPtr, this.ptr)); } + + fromString(s : string) { + Z3.solver_from_string(contextPtr, this.ptr, s); + throwIfError(); + } } class ModelImpl implements Model { diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index d8bda01a1..57e5c37f8 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -398,6 +398,7 @@ export interface Solver { add(...exprs: (Bool | AstVector>)[]): void; addAndTrack(expr: Bool, constant: Bool | string): void; assertions(): AstVector>; + fromString(s : string): void; check(...exprs: (Bool | AstVector>)[]): Promise; model(): Model; } @@ -616,7 +617,7 @@ export interface Arith extends Expr | number | bigint | string): Arith; /** - * Substract second number from the first one + * Subtract second number from the first one */ sub(other: Arith | number | bigint | string): Arith; /** @@ -708,7 +709,7 @@ export interface RatNum extends Arith { } /** - * A Sort represting Bit Vector numbers of specified {@link BitVecSort.size size} + * A Sort representing Bit Vector numbers of specified {@link BitVecSort.size size} * * @typeParam Bits - A number representing amount of bits for this sort * @category Bit Vectors @@ -877,42 +878,42 @@ export interface BitVecraise_exception("invalid const array definition, invalid domain size"); + m_manager->raise_exception("invalid const array definition, expected one argument"); return nullptr; } if (!is_array_sort(s)) { @@ -326,7 +326,9 @@ func_decl * array_decl_plugin::mk_array_ext(unsigned arity, sort * const * domai } sort * r = to_sort(s->get_parameter(i).get_ast()); parameter param(i); - return m_manager->mk_func_decl(m_array_ext_sym, arity, domain, r, func_decl_info(m_family_id, OP_ARRAY_EXT, 1, ¶m)); + func_decl_info info(func_decl_info(m_family_id, OP_ARRAY_EXT, 1, ¶m)); + info.set_commutative(true); + return m_manager->mk_func_decl(m_array_ext_sym, arity, domain, r, info); } diff --git a/src/ast/array_decl_plugin.h b/src/ast/array_decl_plugin.h index 6fe2c7657..fdad692ac 100644 --- a/src/ast/array_decl_plugin.h +++ b/src/ast/array_decl_plugin.h @@ -178,6 +178,7 @@ public: bool is_default(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_DEFAULT); } bool is_subset(expr const* n) const { return is_app_of(n, m_fid, OP_SET_SUBSET); } bool is_as_array(func_decl* f, func_decl*& g) const { return is_decl_of(f, m_fid, OP_AS_ARRAY) && (g = get_as_array_func_decl(f), true); } + bool is_map(func_decl* f, func_decl*& g) const { return is_map(f) && (g = get_map_func_decl(f), true); } func_decl * get_as_array_func_decl(expr * n) const; func_decl * get_as_array_func_decl(func_decl* f) const; func_decl * get_map_func_decl(func_decl* f) const; diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index c51ea4e32..38f4dcf05 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -889,8 +889,10 @@ void basic_decl_plugin::set_manager(ast_manager * m, family_id id) { } void basic_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { - if (logic == symbol::null) + if (logic == symbol::null) { sort_names.push_back(builtin_name("bool", BOOL_SORT)); + sort_names.push_back(builtin_name("Proof", PROOF_SORT)); // reserved name? + } sort_names.push_back(builtin_name("Bool", BOOL_SORT)); } @@ -3025,11 +3027,23 @@ proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * pro found_complement = true; } } + // patch to deal with lambdas introduced during search. + // lambdas can occur in terms both internalized and in raw form. + if (!found_complement && !is_or(f1) && num_proofs == 2) { + args.push_back(proofs[0]); + args.push_back(proofs[1]); + args.push_back(mk_false()); + found_complement = true; + } + if (!found_complement) { args.append(num_proofs, (expr**)proofs); CTRACE("mk_unit_resolution_bug", !is_or(f1), tout << mk_ll_pp(f1, *this) << "\n"; for (unsigned i = 1; i < num_proofs; ++i) tout << mk_pp(proofs[i], *this) << "\n"; + tout << "facts\n"; + for (unsigned i = 0; i < num_proofs; ++i) + tout << mk_pp(get_fact(proofs[i]), *this) << "\n"; ); SASSERT(is_or(f1)); ptr_buffer new_lits; diff --git a/src/ast/ast.h b/src/ast/ast.h index 798280d2c..512501226 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -531,6 +531,13 @@ public: #endif }; +#define MATCH_QUATARY(_MATCHER_) \ + bool _MATCHER_(expr const* n, expr*& a1, expr*& a2, expr *& a3, expr *& a4) const { \ + if (_MATCHER_(n) && to_app(n)->get_num_args() == 4) { \ + a1 = to_app(n)->get_arg(0); a2 = to_app(n)->get_arg(1); a3 = to_app(n)->get_arg(2); a4 = to_app(n)->get_arg(3); return true; } \ + return false; \ + } + #define MATCH_TERNARY(_MATCHER_) \ bool _MATCHER_(expr const* n, expr*& a1, expr*& a2, expr *& a3) const { \ if (_MATCHER_(n) && to_app(n)->get_num_args() == 3) { \ @@ -1024,7 +1031,7 @@ protected: friend class ast_manager; public: - virtual ~decl_plugin() {} + virtual ~decl_plugin() = default; virtual void finalize() {} @@ -2582,7 +2589,7 @@ class ast_mark { obj_mark m_expr_marks; obj_mark m_decl_marks; public: - virtual ~ast_mark() {} + virtual ~ast_mark() = default; bool is_marked(ast * n) const; virtual void mark(ast * n, bool flag); virtual void reset(); diff --git a/src/ast/ast_pp_util.cpp b/src/ast/ast_pp_util.cpp index 008a7cc9a..a74566199 100644 --- a/src/ast/ast_pp_util.cpp +++ b/src/ast/ast_pp_util.cpp @@ -64,6 +64,17 @@ void ast_pp_util::display_decls(std::ostream& out) { m_rec_decls = n; } +void ast_pp_util::display_skolem_decls(std::ostream& out) { + ast_smt_pp pp(m); + unsigned n = coll.get_num_decls(); + for (unsigned i = m_decls; i < n; ++i) { + func_decl* f = coll.get_func_decls()[i]; + if (f->get_family_id() == null_family_id && !m_removed.contains(f) && f->is_skolem()) + ast_smt2_pp(out, f, m_env) << "\n"; + } + m_decls = n; +} + void ast_pp_util::remove_decl(func_decl* f) { m_removed.insert(f); } @@ -118,6 +129,7 @@ void ast_pp_util::push() { m_rec_decls.push(); m_decls.push(); m_sorts.push(); + m_defined_lim.push_back(m_defined.size()); } void ast_pp_util::pop(unsigned n) { @@ -125,4 +137,55 @@ void ast_pp_util::pop(unsigned n) { m_rec_decls.pop(n); m_decls.pop(n); m_sorts.pop(n); + unsigned old_sz = m_defined_lim[m_defined_lim.size() - n]; + for (unsigned i = m_defined.size(); i-- > old_sz; ) + m_is_defined.mark(m_defined.get(i), false); + m_defined.shrink(old_sz); + m_defined_lim.shrink(m_defined_lim.size() - n); +} + +std::ostream& ast_pp_util::display_expr_def(std::ostream& out, expr* n) { + if (is_app(n) && to_app(n)->get_num_args() == 0) + return out << mk_pp(n, m); + else + return out << "$" << n->get_id(); +} + +std::ostream& ast_pp_util::define_expr(std::ostream& out, expr* n) { + ptr_buffer visit; + visit.push_back(n); + while (!visit.empty()) { + n = visit.back(); + if (m_is_defined.is_marked(n)) { + visit.pop_back(); + continue; + } + if (is_app(n)) { + bool all_visit = true; + for (auto* e : *to_app(n)) { + if (m_is_defined.is_marked(e)) + continue; + all_visit = false; + visit.push_back(e); + } + if (!all_visit) + continue; + m_defined.push_back(n); + m_is_defined.mark(n, true); + visit.pop_back(); + if (to_app(n)->get_num_args() > 0) { + out << "(define-const $" << n->get_id() << " " << mk_pp(n->get_sort(), m) << " ("; + out << mk_ismt2_func(to_app(n)->get_decl(), m); + for (auto* e : *to_app(n)) + display_expr_def(out << " ", e); + out << "))\n"; + } + continue; + } + out << "(define-const $" << n->get_id() << " " << mk_pp(n->get_sort(), m) << " " << mk_pp(n, m) << ")\n"; + m_defined.push_back(n); + m_is_defined.mark(n, true); + visit.pop_back(); + } + return out; } diff --git a/src/ast/ast_pp_util.h b/src/ast/ast_pp_util.h index 06ade7eed..9cec62267 100644 --- a/src/ast/ast_pp_util.h +++ b/src/ast/ast_pp_util.h @@ -30,15 +30,18 @@ class ast_pp_util { stacked_value m_rec_decls; stacked_value m_decls; stacked_value m_sorts; + expr_mark m_is_defined; + expr_ref_vector m_defined; + unsigned_vector m_defined_lim; public: decl_collector coll; + + ast_pp_util(ast_manager& m): m(m), m_env(m), m_rec_decls(0), m_decls(0), m_sorts(0), m_defined(m), coll(m) {} - ast_pp_util(ast_manager& m): m(m), m_env(m), m_rec_decls(0), m_decls(0), m_sorts(0), coll(m) {} - - void reset() { coll.reset(); m_removed.reset(); m_sorts.clear(0u); m_decls.clear(0u); m_rec_decls.clear(0u); } - + void reset() { coll.reset(); m_removed.reset(); m_sorts.clear(0u); m_decls.clear(0u); m_rec_decls.clear(0u); + m_is_defined.reset(); m_defined.reset(); m_defined_lim.reset(); } void collect(expr* e); @@ -50,6 +53,8 @@ class ast_pp_util { void display_decls(std::ostream& out); + void display_skolem_decls(std::ostream& out); + void display_asserts(std::ostream& out, expr_ref_vector const& fmls, bool neat = true); void display_assert(std::ostream& out, expr* f, bool neat = true); @@ -58,6 +63,10 @@ class ast_pp_util { std::ostream& display_expr(std::ostream& out, expr* f, bool neat = true); + std::ostream& define_expr(std::ostream& out, expr* f); + + std::ostream& display_expr_def(std::ostream& out, expr* f); + void push(); void pop(unsigned n); diff --git a/src/ast/ast_printer.h b/src/ast/ast_printer.h index 37023393f..bea01e826 100644 --- a/src/ast/ast_printer.h +++ b/src/ast/ast_printer.h @@ -24,7 +24,7 @@ Revision History: class ast_printer { public: - virtual ~ast_printer() {} + virtual ~ast_printer() = default; virtual void pp(sort * s, format_ns::format_ref & r) const { UNREACHABLE(); } virtual void pp(func_decl * f, format_ns::format_ref & r) const { UNREACHABLE(); } virtual void pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) const { UNREACHABLE(); } diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index dbf9340ad..47649b9b2 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -46,7 +46,7 @@ protected: format_ns::format * pp_as(format_ns::format * fname, sort * s); format_ns::format * pp_signature(format_ns::format * f_name, func_decl * f); public: - virtual ~smt2_pp_environment() {} + virtual ~smt2_pp_environment() = default; virtual ast_manager & get_manager() const = 0; virtual arith_util & get_autil() = 0; virtual bv_util & get_bvutil() = 0; diff --git a/src/ast/bv_decl_plugin.cpp b/src/ast/bv_decl_plugin.cpp index 5b28bcf5e..351d037cb 100644 --- a/src/ast/bv_decl_plugin.cpp +++ b/src/ast/bv_decl_plugin.cpp @@ -620,7 +620,7 @@ func_decl * bv_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p for (unsigned i = 0; i < num_args; ++i) { if (args[i]->get_sort() != r->get_domain(i)) { std::ostringstream buffer; - buffer << "Argument " << mk_pp(args[i], m) << " at position " << i << " does not match declaration " << mk_pp(r, m); + buffer << "Argument " << mk_pp(args[i], m) << " at position " << i << " has sort " << mk_pp(args[i]->get_sort(), m) << " it does does not match declaration " << mk_pp(r, m); m.raise_exception(buffer.str()); return nullptr; } diff --git a/src/ast/datatype_decl_plugin.cpp b/src/ast/datatype_decl_plugin.cpp index 11c33d695..041d6740a 100644 --- a/src/ast/datatype_decl_plugin.cpp +++ b/src/ast/datatype_decl_plugin.cpp @@ -635,6 +635,28 @@ namespace datatype { } } + bool plugin::are_distinct(app * a, app * b) const { + if (a == b) + return false; + if (is_unique_value(a) && is_unique_value(b)) + return true; + if (u().is_constructor(a) && u().is_constructor(b)) { + if (a->get_decl() != b->get_decl()) + return true; + for (unsigned i = a->get_num_args(); i-- > 0; ) { + if (!is_app(a->get_arg(i))) + continue; + if (!is_app(b->get_arg(i))) + continue; + app* _a = to_app(a->get_arg(i)); + app* _b = to_app(b->get_arg(i)); + if (m_manager->are_distinct(_a, _b)) + return true; + } + } + return false; + } + expr * plugin::get_some_value(sort * s) { SASSERT(u().is_datatype(s)); func_decl * c = u().get_non_rec_constructor(s); @@ -882,18 +904,16 @@ namespace datatype { bool util::is_well_founded(unsigned num_types, sort* const* sorts) { buffer well_founded(num_types, false); obj_map sort2id; - for (unsigned i = 0; i < num_types; ++i) { + for (unsigned i = 0; i < num_types; ++i) sort2id.insert(sorts[i], i); - } unsigned num_well_founded = 0, id = 0; bool changed; ptr_vector subsorts; do { changed = false; for (unsigned tid = 0; tid < num_types; tid++) { - if (well_founded[tid]) { + if (well_founded[tid]) continue; - } sort* s = sorts[tid]; def const& d = get_def(s); for (constructor const* c : d) { @@ -901,9 +921,12 @@ namespace datatype { subsorts.reset(); get_subsorts(a->range(), subsorts); for (sort* srt : subsorts) { - if (sort2id.find(srt, id) && !well_founded[id]) { - goto next_constructor; + if (sort2id.find(srt, id)) { + if (!well_founded[id]) + goto next_constructor; } + else if (is_datatype(srt)) + break; } } changed = true; diff --git a/src/ast/datatype_decl_plugin.h b/src/ast/datatype_decl_plugin.h index 0698bf821..4561dfacf 100644 --- a/src/ast/datatype_decl_plugin.h +++ b/src/ast/datatype_decl_plugin.h @@ -105,7 +105,7 @@ namespace datatype { class size { unsigned m_ref{ 0 }; public: - virtual ~size() { } + virtual ~size() = default; void inc_ref() { ++m_ref; } void dec_ref(); static size* mk_offset(sort_size const& s); @@ -233,6 +233,8 @@ namespace datatype { bool is_value(app* e) const override { return is_value_aux(false, e); } bool is_unique_value(app * e) const override { return is_value_aux(true, e); } + + bool are_distinct(app * a, app * b) const override; void get_op_names(svector & op_names, symbol const & logic) override; diff --git a/src/ast/decl_collector.cpp b/src/ast/decl_collector.cpp index ecad96521..5b634abbd 100644 --- a/src/ast/decl_collector.cpp +++ b/src/ast/decl_collector.cpp @@ -48,6 +48,7 @@ bool decl_collector::is_bool(sort * s) { } void decl_collector::visit_func(func_decl * n) { + func_decl* g; if (!m_visited.is_marked(n)) { family_id fid = n->get_family_id(); if (fid == null_family_id) @@ -57,6 +58,8 @@ void decl_collector::visit_func(func_decl * n) { recfun::util u(m()); m_todo.push_back(u.get_def(n).get_rhs()); } + else if (m_ar_util.is_as_array(n, g)) + m_todo.push_back(g); m_visited.mark(n, true); m_trail.push_back(n); } @@ -65,7 +68,8 @@ void decl_collector::visit_func(func_decl * n) { decl_collector::decl_collector(ast_manager & m): m_manager(m), m_trail(m), - m_dt_util(m) { + m_dt_util(m), + m_ar_util(m) { m_basic_fid = m_manager.get_basic_family_id(); m_dt_fid = m_dt_util.get_family_id(); recfun::util rec_util(m); @@ -156,8 +160,15 @@ void decl_collector::collect_deps(sort* s, sort_set& set) { for (unsigned i = 0; i < num_cnstr; i++) { func_decl * cnstr = m_dt_util.get_datatype_constructors(s)->get(i); set.insert(cnstr->get_range()); - for (unsigned j = 0; j < cnstr->get_arity(); ++j) - set.insert(cnstr->get_domain(j)); + for (unsigned j = 0; j < cnstr->get_arity(); ++j) { + sort* n = cnstr->get_domain(j); + set.insert(n); + for (unsigned i = n->get_num_parameters(); i-- > 0; ) { + parameter const& p = n->get_parameter(i); + if (p.is_ast() && is_sort(p.get_ast())) + set.insert(to_sort(p.get_ast())); + } + } } } diff --git a/src/ast/decl_collector.h b/src/ast/decl_collector.h index 876b97188..31cabe796 100644 --- a/src/ast/decl_collector.h +++ b/src/ast/decl_collector.h @@ -23,6 +23,7 @@ Revision History: #include "util/lim_vector.h" #include "ast/ast.h" #include "ast/datatype_decl_plugin.h" +#include "ast/array_decl_plugin.h" class decl_collector { ast_manager & m_manager; @@ -35,6 +36,7 @@ class decl_collector { family_id m_basic_fid; family_id m_dt_fid; datatype_util m_dt_util; + array_util m_ar_util; family_id m_rec_fid; ptr_vector m_todo; diff --git a/src/ast/expr_functors.h b/src/ast/expr_functors.h index 4e87fb39e..af669f48e 100644 --- a/src/ast/expr_functors.h +++ b/src/ast/expr_functors.h @@ -27,14 +27,14 @@ Revision History: class i_expr_pred { public: virtual bool operator()(expr* e) = 0; - virtual ~i_expr_pred() {} + virtual ~i_expr_pred() = default; }; class i_sort_pred { public: virtual bool operator()(sort* s) = 0; - virtual ~i_sort_pred() {} + virtual ~i_sort_pred() = default; }; diff --git a/src/ast/fpa/fpa2bv_converter.cpp b/src/ast/fpa/fpa2bv_converter.cpp index 862fe6929..18baba57c 100644 --- a/src/ast/fpa/fpa2bv_converter.cpp +++ b/src/ast/fpa/fpa2bv_converter.cpp @@ -147,11 +147,36 @@ void fpa2bv_converter::mk_distinct(func_decl * f, unsigned num, expr * const * a void fpa2bv_converter::mk_numeral(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { SASSERT(num == 0); - SASSERT(f->get_num_parameters() == 1); - SASSERT(f->get_parameter(0).is_external()); - unsigned p_id = f->get_parameter(0).get_ext_id(); - mpf const & v = m_plugin->get_value(p_id); - mk_numeral(f->get_range(), v, result); + sort* s = f->get_range(); + if (f->get_num_parameters() == 1) { + SASSERT(f->get_parameter(0).is_external()); + unsigned p_id = f->get_parameter(0).get_ext_id(); + mpf const& v = m_plugin->get_value(p_id); + mk_numeral(s, v, result); + return; + } + scoped_mpf v(m_mpf_manager); + unsigned ebits = m_util.get_ebits(s), sbits = m_util.get_sbits(s); + switch (f->get_decl_kind()) { + case OP_FPA_PLUS_INF: + m_util.fm().mk_pinf(ebits, sbits, v); + break; + case OP_FPA_MINUS_INF: + m_util.fm().mk_ninf(ebits, sbits, v); + break; + case OP_FPA_NAN: + m_util.fm().mk_nan(ebits, sbits, v); + break; + case OP_FPA_PLUS_ZERO: + m_util.fm().mk_pzero(ebits, sbits, v); + break; + case OP_FPA_MINUS_ZERO: + m_util.fm().mk_nzero(ebits, sbits, v); + break; + default: + UNREACHABLE(); + } + mk_numeral(s, v, result); } void fpa2bv_converter::mk_numeral(sort * s, mpf const & v, expr_ref & result) { @@ -3406,8 +3431,14 @@ void fpa2bv_converter::mk_to_bv(func_decl * f, unsigned num, expr * const * args } else { expr_ref ll(m); - ll = m_bv_util.mk_sign_extend(3, m_bv_util.mk_concat(bv1, m_bv_util.mk_numeral(0, bv_sz-1))); - ul = m_bv_util.mk_zero_extend(4, m_bv_util.mk_bv_neg(m_bv_util.mk_numeral(1, bv_sz-1))); + ll = bv1; + if (bv_sz > 1) + ll = m_bv_util.mk_concat(bv1, m_bv_util.mk_numeral(0, bv_sz - 1)); + ll = m_bv_util.mk_sign_extend(3, ll); + if (bv_sz > 1) + ul = m_bv_util.mk_zero_extend(4, m_bv_util.mk_bv_neg(m_bv_util.mk_numeral(1, bv_sz - 1))); + else + ul = m_bv_util.mk_numeral(0, 4); ovfl = m.mk_or(ovfl, m_bv_util.mk_sle(pre_rounded, m_bv_util.mk_bv_neg(m_bv_util.mk_numeral(1, bv_sz + 3)))); pre_rounded = m.mk_ite(x_is_neg, m_bv_util.mk_bv_neg(pre_rounded), pre_rounded); in_range = m.mk_and(m.mk_not(ovfl), diff --git a/src/ast/macros/macro_finder.cpp b/src/ast/macros/macro_finder.cpp index ec2c166c9..e7452ee9c 100644 --- a/src/ast/macros/macro_finder.cpp +++ b/src/ast/macros/macro_finder.cpp @@ -376,7 +376,17 @@ bool macro_finder::expand_macros(unsigned num, justified_expr const * fmls, vect return found_new_macro; } +void macro_finder::revert_unsafe_macros(vector& new_fmls) { + auto& unsafe_macros = m_macro_manager.unsafe_macros(); + for (auto* f : unsafe_macros) { + quantifier* q = m_macro_manager.get_macro_quantifier(f); + new_fmls.push_back(justified_expr(m, q, nullptr)); + } + unsafe_macros.reset(); +} + void macro_finder::operator()(unsigned n, justified_expr const* fmls, vector& new_fmls) { + m_macro_manager.unsafe_macros().reset(); TRACE("macro_finder", tout << "processing macros...\n";); vector _new_fmls; if (expand_macros(n, fmls, _new_fmls)) { @@ -388,6 +398,7 @@ void macro_finder::operator()(unsigned n, justified_expr const* fmls, vector& new_fmls); + public: macro_finder(ast_manager & m, macro_manager & mm); ~macro_finder(); diff --git a/src/ast/macros/macro_manager.cpp b/src/ast/macros/macro_manager.cpp index 254663c66..032c724e1 100644 --- a/src/ast/macros/macro_manager.cpp +++ b/src/ast/macros/macro_manager.cpp @@ -241,12 +241,14 @@ func_decl * macro_manager::get_macro_interpretation(unsigned i, expr_ref & inter struct macro_manager::macro_expander_cfg : public default_rewriter_cfg { ast_manager& m; macro_manager& mm; + array_util a; expr_dependency_ref m_used_macro_dependencies; expr_ref_vector m_trail; macro_expander_cfg(ast_manager& m, macro_manager& mm): m(m), mm(mm), + a(m), m_used_macro_dependencies(m), m_trail(m) {} @@ -296,7 +298,7 @@ struct macro_manager::macro_expander_cfg : public default_rewriter_cfg { return false; app * n = to_app(_n); quantifier * q = nullptr; - func_decl * d = n->get_decl(); + func_decl * d = n->get_decl(), *d2 = nullptr; TRACE("macro_manager", tout << "trying to expand:\n" << mk_pp(n, m) << "\nd:\n" << d->get_name() << "\n";); if (mm.m_decl2macro.find(d, q)) { app * head = nullptr; @@ -343,6 +345,12 @@ struct macro_manager::macro_expander_cfg : public default_rewriter_cfg { m_used_macro_dependencies = m.mk_join(m_used_macro_dependencies, ed); return true; } + else if (a.is_as_array(d, d2) && mm.m_decl2macro.find(d2, q)) { + mm.unsafe_macros().insert(d2); + } + else if (a.is_map(d, d2) && mm.m_decl2macro.find(d2, q)) { + mm.unsafe_macros().insert(d2); + } return false; } }; diff --git a/src/ast/macros/macro_manager.h b/src/ast/macros/macro_manager.h index 6aac12114..57583b67b 100644 --- a/src/ast/macros/macro_manager.h +++ b/src/ast/macros/macro_manager.h @@ -29,7 +29,7 @@ Revision History: \brief Macros are universally quantified formulas of the form: (forall X (= (f X) T[X])) (forall X (iff (f X) T[X])) - where T[X] does not contain X. + where T[X] does not contain f. This class is responsible for storing macros and expanding them. It has support for backtracking and tagging declarations in an expression as forbidded for being macros. @@ -47,6 +47,7 @@ class macro_manager { expr_dependency_ref_vector m_macro_deps; obj_hashtable m_forbidden_set; func_decl_ref_vector m_forbidden; + obj_hashtable m_unsafe_macros; struct scope { unsigned m_decls_lim; unsigned m_forbidden_lim; @@ -86,7 +87,7 @@ public: quantifier * get_macro_quantifier(func_decl * f) const { quantifier * q = nullptr; m_decl2macro.find(f, q); return q; } void get_head_def(quantifier * q, func_decl * d, app * & head, expr_ref & def, bool& revert) const; void expand_macros(expr * n, proof * pr, expr_dependency * dep, expr_ref & r, proof_ref & new_pr, expr_dependency_ref & new_dep); - + obj_hashtable& unsafe_macros() { return m_unsafe_macros; } }; diff --git a/src/ast/macros/quantifier_macro_info.h b/src/ast/macros/quantifier_macro_info.h index 82790e937..646b31c88 100644 --- a/src/ast/macros/quantifier_macro_info.h +++ b/src/ast/macros/quantifier_macro_info.h @@ -39,7 +39,7 @@ protected: void collect_macro_candidates(quantifier* q); public: quantifier_macro_info(ast_manager& m, quantifier* q); - virtual ~quantifier_macro_info() {} + virtual ~quantifier_macro_info() = default; bool is_auf() const { return m_is_auf; } quantifier* get_flat_q() const { return m_flat_q; } bool has_cond_macros() const { return !m_cond_macros.empty(); } diff --git a/src/ast/normal_forms/elim_term_ite.h b/src/ast/normal_forms/elim_term_ite.h index 60f2ad8f7..36302ad1e 100644 --- a/src/ast/normal_forms/elim_term_ite.h +++ b/src/ast/normal_forms/elim_term_ite.h @@ -31,7 +31,7 @@ public: elim_term_ite_cfg(ast_manager & m, defined_names & d): m(m), m_defined_names(d) { // TBD enable_ac_support(false); } - virtual ~elim_term_ite_cfg() {} + virtual ~elim_term_ite_cfg() = default; vector const& new_defs() const { return m_new_defs; } br_status reduce_app(func_decl* f, unsigned n, expr *const* args, expr_ref& result, proof_ref& result_pr); void push() { m_lim.push_back(m_new_defs.size()); } diff --git a/src/ast/normal_forms/name_exprs.h b/src/ast/normal_forms/name_exprs.h index 268df8821..4f652bce8 100644 --- a/src/ast/normal_forms/name_exprs.h +++ b/src/ast/normal_forms/name_exprs.h @@ -29,7 +29,7 @@ public: class name_exprs { public: - virtual ~name_exprs() {} + virtual ~name_exprs() = default; virtual void operator()(expr * n, // [IN] expression that contain the sub-expressions to be named expr_ref_vector & new_defs, // [OUT] new definitions proof_ref_vector & new_def_proofs, // [OUT] proofs of the new definitions diff --git a/src/ast/pp.cpp b/src/ast/pp.cpp index e307989c9..091b50c97 100644 --- a/src/ast/pp.cpp +++ b/src/ast/pp.cpp @@ -66,7 +66,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p) bool single_line = p.single_line(); unsigned pos = 0; - unsigned ribbon_pos = 0; unsigned line = 0; unsigned len; unsigned i; @@ -92,7 +91,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p) break; } pos += len; - ribbon_pos += len; out << f->get_decl()->get_parameter(0).get_symbol(); break; case OP_INDENT: @@ -121,7 +119,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p) break; } pos = indent; - ribbon_pos = 0; line++; if (line < max_num_lines) { out << "\n"; diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 93e10f094..bf865b393 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -291,7 +291,11 @@ namespace recfun { expr * e = stack.back(); stack.pop_back(); - if (m.is_ite(e)) { + expr* cond = nullptr, *th = nullptr, *el = nullptr; + if (m.is_ite(e, cond, th, el) && contains_def(u, cond)) { + // skip + } + else if (m.is_ite(e)) { // need to do a case split on `e`, forking the search space b.to_split = st.cons_ite(to_app(e), b.to_split); } @@ -338,9 +342,8 @@ namespace recfun { // substitute, to get rid of `ite` terms expr_ref case_rhs = subst(rhs); - for (unsigned i = 0; i < conditions.size(); ++i) { + for (unsigned i = 0; i < conditions.size(); ++i) conditions[i] = subst(conditions.get(i)); - } // yield new case bool is_imm = is_i(case_rhs); @@ -471,9 +474,8 @@ namespace recfun { void plugin::set_definition(replace& r, promise_def & d, bool is_macro, unsigned n_vars, var * const * vars, expr * rhs) { u().set_definition(r, d, is_macro, n_vars, vars, rhs); - for (case_def & c : d.get_def()->get_cases()) { + for (case_def & c : d.get_def()->get_cases()) m_case_defs.insert(c.get_decl(), &c); - } } bool plugin::has_defs() const { diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index bbc4e5810..dcff35e82 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -49,7 +49,7 @@ namespace recfun { class replace { public: - virtual ~replace() {} + virtual ~replace() = default; virtual void reset() = 0; virtual void insert(expr* d, expr* r) = 0; virtual expr_ref operator()(expr* e) = 0; diff --git a/src/ast/rewriter/arith_rewriter.cpp b/src/ast/rewriter/arith_rewriter.cpp index 35b45295f..cdf09d7f3 100644 --- a/src/ast/rewriter/arith_rewriter.cpp +++ b/src/ast/rewriter/arith_rewriter.cpp @@ -706,6 +706,31 @@ br_status arith_rewriter::mk_eq_core(expr * arg1, expr * arg2, expr_ref & result return st; } +br_status arith_rewriter::mk_and_core(unsigned n, expr* const* args, expr_ref& result) { + if (n <= 1) + return BR_FAILED; + expr* x, * y, * z, * u; + rational a, b; + if (m_util.is_le(args[0], x, y) && m_util.is_numeral(x, a)) { + expr* arg0 = args[0]; + ptr_buffer rest; + for (unsigned i = 1; i < n; ++i) { + if (m_util.is_le(args[i], z, u) && u == y && m_util.is_numeral(z, b)) { + if (b > a) + arg0 = args[i]; + } + else + rest.push_back(args[i]); + } + if (rest.size() < n - 1) { + rest.push_back(arg0); + result = m().mk_and(rest); + return BR_REWRITE1; + } + } + return BR_FAILED; +} + bool arith_rewriter::mk_eq_mod(expr* arg1, expr* arg2, expr_ref& result) { expr* x = nullptr, *y = nullptr, *z = nullptr, *u = nullptr; rational p, k, l; diff --git a/src/ast/rewriter/arith_rewriter.h b/src/ast/rewriter/arith_rewriter.h index 19a8363a0..c80226d0c 100644 --- a/src/ast/rewriter/arith_rewriter.h +++ b/src/ast/rewriter/arith_rewriter.h @@ -149,6 +149,8 @@ public: br_status mk_abs_core(expr * arg, expr_ref & result); + br_status mk_and_core(unsigned n, expr* const* args, expr_ref& result); + br_status mk_div_core(expr * arg1, expr * arg2, expr_ref & result); br_status mk_idiv_core(expr * arg1, expr * arg2, expr_ref & result); br_status mk_idivides(unsigned k, expr * arg, expr_ref & result); diff --git a/src/ast/rewriter/array_rewriter.cpp b/src/ast/rewriter/array_rewriter.cpp index 40e3e532f..dd0e7e869 100644 --- a/src/ast/rewriter/array_rewriter.cpp +++ b/src/ast/rewriter/array_rewriter.cpp @@ -86,12 +86,11 @@ br_status array_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * c // l_true -- all equal // l_false -- at least one disequal // l_undef -- don't know -template lbool array_rewriter::compare_args(unsigned num_args, expr * const * args1, expr * const * args2) { for (unsigned i = 0; i < num_args; i++) { if (args1[i] == args2[i]) continue; - if (CHECK_DISEQ && m().are_distinct(args1[i], args2[i])) + if (m().are_distinct(args1[i], args2[i])) return l_false; return l_undef; } @@ -102,9 +101,7 @@ br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, SASSERT(num_args >= 3); if (m_util.is_store(args[0])) { - lbool r = m_sort_store ? - compare_args(num_args - 2, args + 1, to_app(args[0])->get_args() + 1) : - compare_args(num_args - 2, args + 1, to_app(args[0])->get_args() + 1); + lbool r = compare_args(num_args - 2, args + 1, to_app(args[0])->get_args() + 1); switch (r) { case l_true: { // @@ -118,12 +115,11 @@ br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, return BR_DONE; } case l_false: - SASSERT(m_sort_store); // // store(store(a,i,v),j,w) -> store(store(a,j,w),i,v) // if i, j are different, lt(i,j) - // - if (lex_lt(num_args-2, args+1, to_app(args[0])->get_args() + 1)) { + // + if (m_sort_store && lex_lt(num_args-2, args+1, to_app(args[0])->get_args() + 1)) { ptr_buffer new_args; new_args.push_back(to_app(args[0])->get_arg(0)); new_args.append(num_args-1, args+1); @@ -134,6 +130,9 @@ br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, result = m().mk_app(get_fid(), OP_STORE, num_args, new_args.data()); return BR_REWRITE2; } + if (squash_store(num_args, args, result)) + return BR_REWRITE2; + break; case l_undef: break; @@ -155,7 +154,7 @@ br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, // store(a, i, select(a, i)) --> a // if (m_util.is_select(v) && - compare_args(num_args-1, args, to_app(v)->get_args())) { + l_true == compare_args(num_args-1, args, to_app(v)->get_args())) { result = args[0]; return BR_DONE; } @@ -163,19 +162,52 @@ br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, return BR_FAILED; } +bool array_rewriter::squash_store(unsigned n, expr* const* args, expr_ref& result) { + ptr_buffer parents, sargs; + expr* a = args[0]; + while (m_util.is_store(a)) { + lbool r = compare_args(n - 2, args + 1, to_app(a)->get_args() + 1); + switch (r) { + case l_undef: + return false; + case l_true: + result = to_app(a)->get_arg(0); + for (unsigned i = parents.size(); i-- > 0; ) { + expr* p = parents[i]; + sargs.reset(); + sargs.push_back(result); + for (unsigned j = 1; j < to_app(p)->get_num_args(); ++j) + sargs.push_back(to_app(p)->get_arg(j)); + result = m_util.mk_store(sargs.size(), sargs.data()); + } + sargs.reset(); + sargs.push_back(result); + for (unsigned j = 1; j < n; ++j) + sargs.push_back(args[j]); + result = m_util.mk_store(sargs.size(), sargs.data()); + return true; + case l_false: + parents.push_back(a); + a = to_app(a)->get_arg(0); + break; + } + } + return false; +} + br_status array_rewriter::mk_select_core(unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(num_args >= 2); if (m_util.is_store(args[0])) { SASSERT(to_app(args[0])->get_num_args() == num_args+1); - switch (compare_args(num_args - 1, args+1, to_app(args[0])->get_args()+1)) { + switch (compare_args(num_args - 1, args+1, to_app(args[0])->get_args()+1)) { case l_true: // select(store(a, I, v), I) --> v result = to_app(args[0])->get_arg(num_args); return BR_DONE; case l_false: { expr* arg0 = to_app(args[0])->get_arg(0); - while (m_util.is_store(arg0) && compare_args(num_args-1, args + 1, to_app(arg0)->get_args() + 1) == l_false) { + while (m_util.is_store(arg0) && compare_args(num_args-1, args + 1, to_app(arg0)->get_args() + 1) == l_false) { arg0 = to_app(arg0)->get_arg(0); } diff --git a/src/ast/rewriter/array_rewriter.h b/src/ast/rewriter/array_rewriter.h index 943cae4e5..4e52b237e 100644 --- a/src/ast/rewriter/array_rewriter.h +++ b/src/ast/rewriter/array_rewriter.h @@ -28,13 +28,13 @@ Notes: */ class array_rewriter { array_util m_util; - bool m_sort_store { false }; - bool m_blast_select_store { false }; - bool m_expand_select_store { false }; - bool m_expand_store_eq { false }; - bool m_expand_select_ite { false }; - bool m_expand_nested_stores { false }; - template + bool m_sort_store = false; + bool m_blast_select_store = false; + bool m_expand_select_store = false; + bool m_expand_store_eq = false; + bool m_expand_select_ite = false; + bool m_expand_nested_stores = false; + lbool compare_args(unsigned num_args, expr * const * args1, expr * const * args2); void mk_eq(expr* e, expr* lhs, expr* rhs, expr_ref_vector& fmls); @@ -45,6 +45,8 @@ class array_rewriter { bool is_expandable_store(expr* s); expr_ref expand_store(expr* s); + bool squash_store(unsigned n, expr* const* args, expr_ref& result); + public: array_rewriter(ast_manager & m, params_ref const & p = params_ref()): m_util(m) { diff --git a/src/ast/rewriter/bool_rewriter.cpp b/src/ast/rewriter/bool_rewriter.cpp index 1e58db340..fb2b0795b 100644 --- a/src/ast/rewriter/bool_rewriter.cpp +++ b/src/ast/rewriter/bool_rewriter.cpp @@ -175,7 +175,7 @@ br_status bool_rewriter::mk_flat_and_core(unsigned num_args, expr * const * args } if (mk_nflat_and_core(flat_args.size(), flat_args.data(), result) == BR_FAILED) result = m().mk_and(flat_args); - return BR_DONE; + return BR_REWRITE1; } return mk_nflat_and_core(num_args, args, result); } @@ -874,7 +874,7 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re expr_ref tmp(m()); mk_not(c, tmp); mk_and(tmp, e, result); - return BR_DONE; + return BR_REWRITE1; } } if (m().is_true(e) && m_elim_ite) { @@ -885,11 +885,11 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re } if (m().is_false(e) && m_elim_ite) { mk_and(c, t, result); - return BR_DONE; + return BR_REWRITE1; } if (c == e && m_elim_ite) { mk_and(c, t, result); - return BR_DONE; + return BR_REWRITE1; } if (c == t && m_elim_ite) { mk_or(c, e, result); @@ -912,13 +912,13 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re expr_ref a(m()); mk_and(c, t2, a); result = m().mk_not(m().mk_eq(t1, a)); - return BR_REWRITE2; + return BR_REWRITE3; } if (m().is_not(t, t1) && m().is_eq(t1, t2, t1) && e == t1) { expr_ref a(m()); mk_and(c, t2, a); result = m().mk_eq(t1, a); - return BR_REWRITE2; + return BR_REWRITE3; } #endif @@ -931,14 +931,14 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re expr_ref new_c(m()); mk_and(c, not_c2, new_c); result = m().mk_ite(new_c, to_app(t)->get_arg(2), e); - return BR_REWRITE1; + return BR_REWRITE2; } // (ite c1 (ite c2 t1 t2) t2) ==> (ite (and c1 c2) t1 t2) if (e == to_app(t)->get_arg(2)) { expr_ref new_c(m()); mk_and(c, to_app(t)->get_arg(0), new_c); result = m().mk_ite(new_c, to_app(t)->get_arg(1), e); - return BR_REWRITE1; + return BR_REWRITE2; } @@ -955,7 +955,7 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re expr_ref new_c(m()); mk_or(and1, and2, new_c); result = m().mk_ite(new_c, to_app(t)->get_arg(1), to_app(t)->get_arg(2)); - return BR_REWRITE1; + return BR_REWRITE3; } // (ite c1 (ite c2 t1 t2) (ite c3 t2 t1)) ==> (ite (or (and c1 c2) (and (not c1) (not c3))) t1 t2) @@ -972,7 +972,7 @@ br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & re expr_ref new_c(m()); mk_or(and1, and2, new_c); result = m().mk_ite(new_c, to_app(t)->get_arg(1), to_app(t)->get_arg(2)); - return BR_REWRITE1; + return BR_REWRITE3; } } } diff --git a/src/ast/rewriter/datatype_rewriter.cpp b/src/ast/rewriter/datatype_rewriter.cpp index 97818ce82..ba0155e97 100644 --- a/src/ast/rewriter/datatype_rewriter.cpp +++ b/src/ast/rewriter/datatype_rewriter.cpp @@ -32,6 +32,10 @@ br_status datatype_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr // simplify is_cons(nil) -> false // SASSERT(num_args == 1); + if (m_util.get_datatype_num_constructors(args[0]->get_sort()) == 1) { + result = m().mk_true(); + return BR_DONE; + } if (!is_app(args[0]) || !m_util.is_constructor(to_app(args[0]))) return BR_FAILED; if (to_app(args[0])->get_decl() == m_util.get_recognizer_constructor(f)) diff --git a/src/ast/rewriter/expr_replacer.h b/src/ast/rewriter/expr_replacer.h index e587faad7..82982adff 100644 --- a/src/ast/rewriter/expr_replacer.h +++ b/src/ast/rewriter/expr_replacer.h @@ -28,7 +28,7 @@ Notes: class expr_replacer { struct scoped_set_subst; public: - virtual ~expr_replacer() {} + virtual ~expr_replacer() = default; virtual ast_manager & m() const = 0; virtual void set_substitution(expr_substitution * s) = 0; diff --git a/src/ast/rewriter/expr_safe_replace.cpp b/src/ast/rewriter/expr_safe_replace.cpp index 300e82707..2ca3b5e24 100644 --- a/src/ast/rewriter/expr_safe_replace.cpp +++ b/src/ast/rewriter/expr_safe_replace.cpp @@ -190,3 +190,17 @@ void expr_safe_replace::apply_substitution(expr* s, expr* def, expr_ref& t) { (*this)(t, t); reset(); } + +void expr_safe_replace::push_scope() { + m_limit.push_back(m_src.size()); +} + +void expr_safe_replace::pop_scope(unsigned n) { + unsigned old_sz = m_limit[m_limit.size() - n]; + if (old_sz != m_src.size()) { + m_cache.clear(); + m_src.shrink(old_sz); + m_dst.shrink(old_sz); + } + m_limit.shrink(m_limit.size() - n); +} diff --git a/src/ast/rewriter/expr_safe_replace.h b/src/ast/rewriter/expr_safe_replace.h index 4e8b64232..7f5a3a100 100644 --- a/src/ast/rewriter/expr_safe_replace.h +++ b/src/ast/rewriter/expr_safe_replace.h @@ -28,6 +28,7 @@ class expr_safe_replace { ast_manager& m; expr_ref_vector m_src; expr_ref_vector m_dst; + unsigned_vector m_limit = 0; ptr_vector m_todo, m_args; expr_ref_vector m_refs; std::unordered_map m_cache; @@ -48,5 +49,9 @@ public: void reset(); bool empty() const { return m_src.empty(); } + + void push_scope(); + + void pop_scope(unsigned n); }; diff --git a/src/ast/rewriter/seq_axioms.cpp b/src/ast/rewriter/seq_axioms.cpp index 930abe4e6..c7dde763f 100644 --- a/src/ast/rewriter/seq_axioms.cpp +++ b/src/ast/rewriter/seq_axioms.cpp @@ -509,6 +509,8 @@ namespace seq { } /** + i = last_indexof(t, s): + !contains(t, s) => i = -1 |t| = 0 => |s| = 0 or i = -1 |t| = 0 & |s| = 0 => i = 0 @@ -1258,9 +1260,12 @@ namespace seq { expr_ref emp = mk_eq_empty(a); expr_ref cnt = expr_ref(e, m); add_clause(cnt, ~pref); - add_clause(cnt, ~postf); + add_clause(cnt, emp, ~postf); add_clause(~emp, mk_eq_empty(tail)); add_clause(emp, mk_eq(a, seq.str.mk_concat(head, tail))); + expr* s, *idx; + if (m_sk.is_tail(tail, s, idx)) + add_clause(emp, mk_ge_e(mk_len(s), idx)); } expr_ref axioms::length_limit(expr* s, unsigned k) { diff --git a/src/ast/rewriter/seq_eq_solver.cpp b/src/ast/rewriter/seq_eq_solver.cpp index ca96512f0..6d8734bc9 100644 --- a/src/ast/rewriter/seq_eq_solver.cpp +++ b/src/ast/rewriter/seq_eq_solver.cpp @@ -438,6 +438,10 @@ namespace seq { !seq.str.is_unit(a) && !seq.str.is_itos(a) && !seq.str.is_nth_i(a) && + !seq.str.is_map(a) && + !seq.str.is_mapi(a) && + !seq.str.is_foldl(a) && + !seq.str.is_foldli(a) && !m.is_ite(a); } diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 4f70b7933..eed36af81 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -2068,7 +2068,7 @@ br_status seq_rewriter::mk_seq_replace_all(expr* a, expr* b, expr* c, expr_ref& */ br_status seq_rewriter::mk_seq_map(expr* f, expr* seqA, expr_ref& result) { if (str().is_empty(seqA)) { - result = str().mk_empty(get_array_range(f->get_sort())); + result = str().mk_empty(str().mk_seq(get_array_range(f->get_sort()))); return BR_DONE; } expr* a, *s1, *s2; @@ -2087,7 +2087,7 @@ br_status seq_rewriter::mk_seq_map(expr* f, expr* seqA, expr_ref& result) { br_status seq_rewriter::mk_seq_mapi(expr* f, expr* i, expr* seqA, expr_ref& result) { if (str().is_empty(seqA)) { - result = str().mk_empty(get_array_range(f->get_sort())); + result = str().mk_empty(str().mk_seq(get_array_range(f->get_sort()))); return BR_DONE; } expr* a, *s1, *s2; diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index 4c7c3883a..cb00938d7 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -83,7 +83,7 @@ public: class expr_solver { public: - virtual ~expr_solver() {} + virtual ~expr_solver() = default; virtual lbool check_sat(expr* e) = 0; }; diff --git a/src/ast/rewriter/seq_skolem.cpp b/src/ast/rewriter/seq_skolem.cpp index c2e948216..96b3df3ed 100644 --- a/src/ast/rewriter/seq_skolem.cpp +++ b/src/ast/rewriter/seq_skolem.cpp @@ -26,10 +26,14 @@ skolem::skolem(ast_manager& m, th_rewriter& rw): m_suffix = "seq.s.prefix"; m_accept = "aut.accept"; m_tail = "seq.tail"; + m_left = "seq.left"; + m_right = "seq.right"; m_seq_first = "seq.first"; m_seq_last = "seq.last"; m_indexof_left = "seq.idx.l"; m_indexof_right = "seq.idx.r"; + m_lindexof_left = "seq.lidx.l"; + m_lindexof_right = "seq.lidx.r"; m_aut_step = "aut.step"; m_pre = "seq.pre"; // (seq.pre s l): prefix of string s of length l m_post = "seq.post"; // (seq.post s l): suffix of string s of length k, based on extract starting at index i of length l @@ -155,6 +159,20 @@ bool skolem::is_tail(expr* e, expr*& s, expr*& idx) const { return is_tail(e) && (s = to_app(e)->get_arg(0), idx = to_app(e)->get_arg(1), true); } +bool skolem::is_left_or_right(expr* e, expr*& x, expr*& y, expr*& z) { + if (!is_skolem(m_left, e) && !is_skolem(m_right, e)) + return false; + x = nullptr; + y = nullptr; + z = nullptr; + unsigned sz = to_app(e)->get_num_args(); + if (sz > 0) x = to_app(e)->get_arg(0); + if (sz > 1) y = to_app(e)->get_arg(1); + if (sz > 2) z = to_app(e)->get_arg(2); + return true; +} + + bool skolem::is_eq(expr* e, expr*& a, expr*& b) const { return is_skolem(m_eq, e) && (a = to_app(e)->get_arg(0), b = to_app(e)->get_arg(1), true); } diff --git a/src/ast/rewriter/seq_skolem.h b/src/ast/rewriter/seq_skolem.h index 088a00eeb..4b828abf6 100644 --- a/src/ast/rewriter/seq_skolem.h +++ b/src/ast/rewriter/seq_skolem.h @@ -32,8 +32,10 @@ namespace seq { symbol m_prefix, m_suffix; symbol m_tail; + symbol m_left, m_right; symbol m_seq_first, m_seq_last; - symbol m_indexof_left, m_indexof_right; // inverse of indexof: (indexof_left s t) + s + (indexof_right s t) = t, for s in t. + symbol m_indexof_left, m_indexof_right; // inverse of indexof: (indexof_left s t) + s + (indexof_right s t) = t, for s in t. + symbol m_lindexof_left, m_lindexof_right; // inverse of indexof: (indexof_left s t) + s + (indexof_right s t) = t, for s in t. symbol m_aut_step; // regex unfolding state symbol m_accept; // regex symbol m_is_empty; // regex emptiness check @@ -81,8 +83,8 @@ namespace seq { expr_ref mk_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk(m_indexof_right, t, s, offset); } expr_ref mk_contains_left(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.cnt.l", t, s, offset); } expr_ref mk_contains_right(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.cnt.r", t, s, offset); } - expr_ref mk_last_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.lidx.l", t, s, offset); } - expr_ref mk_last_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk("seq.lidx.r", t, s, offset); } + expr_ref mk_last_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk(m_lindexof_left, t, s, offset); } + expr_ref mk_last_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk(m_lindexof_right, t, s, offset); } expr_ref mk_tail(expr* s, expr* i) { return mk(m_tail, s, i); } expr_ref mk_post(expr* s, expr* i) { return mk(m_post, s, i); } @@ -100,8 +102,8 @@ namespace seq { expr_ref mk_digit2int(expr* ch) { return mk(symbol("seq.digit2int"), ch, nullptr, nullptr, nullptr, a.mk_int()); } expr_ref mk_digit2bv(expr* ch, sort* bv_sort); expr_ref mk_ubv2ch(expr* b) { return mk(symbol("seq.ubv2ch"), b, nullptr, nullptr, nullptr, seq.mk_char_sort()); } - expr_ref mk_left(expr* x, expr* y, expr* z = nullptr) { return mk("seq.left", x, y, z); } - expr_ref mk_right(expr* x, expr* y, expr* z = nullptr) { return mk("seq.right", x, y, z); } + expr_ref mk_left(expr* x, expr* y, expr* z = nullptr) { return mk(m_left, x, y, z); } + expr_ref mk_right(expr* x, expr* y, expr* z = nullptr) { return mk(m_right, x, y, z); } expr_ref mk_max_unfolding_depth(unsigned d); expr_ref mk_length_limit(expr* e, unsigned d); @@ -117,6 +119,8 @@ namespace seq { bool is_seq_first(expr* e) const { return is_skolem(m_seq_first, e); } bool is_indexof_left(expr* e) const { return is_skolem(m_indexof_left, e); } bool is_indexof_right(expr* e) const { return is_skolem(m_indexof_right, e); } + bool is_last_indexof_left(expr* e) const { return is_skolem(m_lindexof_left, e); } + bool is_last_indexof_right(expr* e) const { return is_skolem(m_lindexof_right, e); } bool is_indexof_left(expr* e, expr*& x, expr*& y) const { return is_indexof_left(e) && (x = to_app(e)->get_arg(0), y = to_app(e)->get_arg(1), true); } @@ -124,6 +128,7 @@ namespace seq { return is_indexof_right(e) && (x = to_app(e)->get_arg(0), y = to_app(e)->get_arg(1), true); } + bool is_left_or_right(expr* e, expr*& x, expr*& y, expr*& z); bool is_step(expr* e) const { return is_skolem(m_aut_step, e); } bool is_step(expr* e, expr*& s, expr*& idx, expr*& re, expr*& i, expr*& j, expr*& t) const; bool is_accept(expr* acc) const { return is_skolem(m_accept, acc); } diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index c69534b08..aa02ab009 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -215,6 +215,11 @@ struct th_rewriter_cfg : public default_rewriter_cfg { if (st != BR_FAILED) return st; } + if (false && k == OP_AND) { + st = m_a_rw.mk_and_core(num, args, result); + if (st != BR_FAILED) + return st; + } if (k == OP_EQ && m_seq_rw.u().has_seq() && is_app(args[0]) && to_app(args[0])->get_family_id() == m_seq_rw.get_fid()) { st = m_seq_rw.mk_eq_core(args[0], args[1], result); diff --git a/src/ast/scoped_proof.h b/src/ast/scoped_proof.h index c8071031c..7943c6eb9 100644 --- a/src/ast/scoped_proof.h +++ b/src/ast/scoped_proof.h @@ -29,8 +29,8 @@ public: m.toggle_proof_mode(mode); } ~scoped_proof_mode() { - m.toggle_proof_mode(m_mode); - } + m.toggle_proof_mode(m_mode); + } }; diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index d5c6da187..c6550d33a 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -403,6 +403,7 @@ public: MATCH_BINARY(is_map); MATCH_TERNARY(is_mapi); MATCH_TERNARY(is_foldl); + MATCH_QUATARY(is_foldli); MATCH_BINARY(is_last_index); MATCH_TERNARY(is_replace); MATCH_TERNARY(is_replace_re); @@ -448,7 +449,7 @@ public: info() {} /* - Used for constructing either an invalid info that is only used to indicate uninitialzed entry, or valid but unknown info value. + Used for constructing either an invalid info that is only used to indicate uninitialized entry, or valid but unknown info value. */ info(lbool is_known) : known(is_known) {} diff --git a/src/ast/substitution/substitution.h b/src/ast/substitution/substitution.h index 336272621..e2329fb83 100644 --- a/src/ast/substitution/substitution.h +++ b/src/ast/substitution/substitution.h @@ -152,7 +152,7 @@ public: return find(to_var(v.get_expr()), v.get_offset(), r); } - void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) { + void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) const { var = m_vars[binding_num]; VERIFY(m_subst.find(var.first, var.second, r)); } diff --git a/src/ast/substitution/substitution_tree.h b/src/ast/substitution/substitution_tree.h index c94bb4eea..13993c171 100644 --- a/src/ast/substitution/substitution_tree.h +++ b/src/ast/substitution/substitution_tree.h @@ -29,7 +29,7 @@ protected: substitution & m_subst; public: st_visitor(substitution & s):m_subst(s) {} - virtual ~st_visitor() {} + virtual ~st_visitor() = default; substitution & get_substitution() { return m_subst; } virtual bool operator()(expr * e) { return true; } }; diff --git a/src/ast/value_generator.h b/src/ast/value_generator.h index 7b77b144e..f3a459389 100644 --- a/src/ast/value_generator.h +++ b/src/ast/value_generator.h @@ -23,7 +23,7 @@ class value_generator_core { public: - virtual ~value_generator_core() {} + virtual ~value_generator_core() = default; virtual family_id get_fid() const = 0; virtual expr_ref get_value(sort* s, unsigned index) = 0; }; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 644bf5c7b..97ae0fbc1 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -561,6 +561,7 @@ cmd_context::~cmd_context() { finalize_cmds(); finalize_tactic_cmds(); finalize_probes(); + m_proof_cmds = nullptr; reset(true); m_mcs.reset(); m_solver = nullptr; @@ -1054,7 +1055,7 @@ func_decl * cmd_context::find_func_decl(symbol const & s, unsigned num_indices, if (f) return f; builtin_decl d; - if (domain && m_builtin_decls.find(s, d)) { + if ((arity == 0 || domain) && m_builtin_decls.find(s, d)) { family_id fid = d.m_fid; decl_kind k = d.m_decl; // Hack: if d.m_next != 0, we use domain[0] (if available) to decide which plugin we use. diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 3dc49624b..512e367ef 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -90,13 +90,24 @@ public: vector::iterator end() const { return m_decls->end(); } }; + +class proof_cmds { +public: + virtual ~proof_cmds() {} + virtual void add_literal(expr* e) = 0; + virtual void end_assumption() = 0; + virtual void end_learned() = 0; + virtual void end_deleted() = 0; +}; + + /** \brief Generic wrapper. */ class object_ref { unsigned m_ref_count = 0; public: - virtual ~object_ref() {} + virtual ~object_ref() = default; virtual void finalize(cmd_context & ctx) = 0; void inc_ref(cmd_context & ctx) { m_ref_count++; @@ -172,6 +183,7 @@ public: bool owns_manager() const { return m_manager != nullptr; } }; + class cmd_context : public progress_callback, public tactic_manager, public ast_printer_context { public: enum status { @@ -191,6 +203,22 @@ public: ~scoped_watch() { m_ctx.m_watch.stop(); } }; + struct scoped_redirect { + cmd_context& m_ctx; + std::ostream& m_verbose; + std::ostream* m_warning; + + scoped_redirect(cmd_context& ctx): m_ctx(ctx), m_verbose(verbose_stream()), m_warning(warning_stream()) { + set_warning_stream(&(*m_ctx.m_diagnostic)); + set_verbose_stream(m_ctx.diagnostic_stream()); + } + + ~scoped_redirect() { + set_verbose_stream(m_verbose); + set_warning_stream(m_warning); + } + }; + protected: @@ -209,6 +237,7 @@ protected: 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; + scoped_ptr m_proof_cmds; static std::ostringstream g_error_stream; @@ -381,6 +410,9 @@ public: pdecl_manager & pm() const { if (!m_pmanager) const_cast(this)->init_manager(); return *m_pmanager; } sexpr_manager & sm() const { if (!m_sexpr_manager) const_cast(this)->m_sexpr_manager = alloc(sexpr_manager); return *m_sexpr_manager; } + proof_cmds* get_proof_cmds() { return m_proof_cmds.get(); } + void set_proof_cmds(proof_cmds* pc) { m_proof_cmds = pc; } + void set_solver_factory(solver_factory * s); void set_check_sat_result(check_sat_result * r) { m_check_sat_result = r; } check_sat_result * get_check_sat_result() const { return m_check_sat_result.get(); } diff --git a/src/cmd_context/extra_cmds/CMakeLists.txt b/src/cmd_context/extra_cmds/CMakeLists.txt index 3aef1a553..1c36745b1 100644 --- a/src/cmd_context/extra_cmds/CMakeLists.txt +++ b/src/cmd_context/extra_cmds/CMakeLists.txt @@ -3,6 +3,7 @@ z3_add_component(extra_cmds dbg_cmds.cpp polynomial_cmds.cpp subpaving_cmds.cpp + proof_cmds.cpp COMPONENT_DEPENDENCIES arith_tactics cmd_context diff --git a/src/cmd_context/extra_cmds/proof_cmds.cpp b/src/cmd_context/extra_cmds/proof_cmds.cpp new file mode 100644 index 000000000..505b0a5ed --- /dev/null +++ b/src/cmd_context/extra_cmds/proof_cmds.cpp @@ -0,0 +1,269 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + proof_cmds.cpp + +Abstract: + + Commands for reading and checking proofs. + +Author: + + Nikolaj Bjorner (nbjorner) 2022-8-26 + +Notes: + +Proof checker for clauses created during search. +1. Clauses annotated by RUP (reverse unit propagation) + are checked to be inferrable using reverse unit propagation + based on previous clauses. +2. Clauses annotated by supported proof rules (proof hints) + are checked by custom proof checkers. There is a proof checker + for each proof rule. Main proof checkers just have a single step + but the framework allows to compose proof rules, each inference + is checked for correctness by a plugin. +3. When there are no supported plugin to justify the derived + clause, or a custom check fails, the fallback is to check that the + derived clause is a consequence of the input clauses using SMT. + The last approach is a bail-out and offers a weaker notion of + self-validation. It is often (but not always) sufficient for using proof + checking for debugging, as the root-cause for an unsound inference in z3 + does not necessarily manifest when checking the conclusion of the + inference. An external proof checker that uses such fallbacks could + use several solvers, or bootstrap from a solver that can generate certificates + when z3 does not. + + + + +--*/ + +#include "util/small_object_allocator.h" +#include "ast/ast_util.h" +#include "smt/smt_solver.h" +#include "sat/sat_solver.h" +#include "sat/sat_drat.h" +#include "sat/smt/euf_proof_checker.h" +#include "cmd_context/cmd_context.h" +#include + +class smt_checker { + ast_manager& m; + params_ref m_params; + + // for checking proof rules (hints) + euf::proof_checker m_checker; + + // for fallback SMT checker + scoped_ptr m_solver; + + // for RUP + symbol m_rup; + sat::solver m_sat_solver; + sat::drat m_drat; + sat::literal_vector m_units; + sat::literal_vector m_clause; + + void add_units() { + auto const& units = m_drat.units(); + for (unsigned i = m_units.size(); i < units.size(); ++i) + m_units.push_back(units[i].first); + } + +public: + smt_checker(ast_manager& m): + m(m), + m_checker(m), + m_sat_solver(m_params, m.limit()), + m_drat(m_sat_solver) + { + m_params.set_bool("drat.check_unsat", true); + m_sat_solver.updt_params(m_params); + m_drat.updt_config(); + m_solver = mk_smt_solver(m, m_params, symbol()); + m_rup = symbol("rup"); + } + + bool is_rup(app* proof_hint) { + return + proof_hint && + proof_hint->get_name() == m_rup; + } + + void mk_clause(expr_ref_vector const& clause) { + m_clause.reset(); + for (expr* e : clause) { + bool sign = false; + while (m.is_not(e, e)) + sign = !sign; + m_clause.push_back(sat::literal(e->get_id(), sign)); + } + } + + void mk_clause(expr* e) { + m_clause.reset(); + bool sign = false; + while (m.is_not(e, e)) + sign = !sign; + m_clause.push_back(sat::literal(e->get_id(), sign)); + } + + bool check_rup(expr_ref_vector const& clause) { + add_units(); + mk_clause(clause); + return m_drat.is_drup(m_clause.size(), m_clause.data(), m_units); + } + + bool check_rup(expr* u) { + add_units(); + mk_clause(u); + return m_drat.is_drup(m_clause.size(), m_clause.data(), m_units); + } + + void add_clause(expr_ref_vector const& clause) { + mk_clause(clause); + m_drat.add(m_clause, sat::status::input()); + } + + void check(expr_ref_vector& clause, app* proof_hint) { + + if (is_rup(proof_hint) && check_rup(clause)) { + std::cout << "(verified-rup)\n"; + return; + } + + expr_ref_vector units(m); + if (m_checker.check(clause, proof_hint, units)) { + bool units_are_rup = true; + for (expr* u : units) { + if (!check_rup(u)) { + std::cout << "unit " << mk_pp(u, m) << " is not rup\n"; + units_are_rup = false; + } + } + if (units_are_rup) { + std::cout << "(verified-" << proof_hint->get_name() << ")\n"; + add_clause(clause); + return; + } + } + + m_solver->push(); + for (expr* lit : clause) + m_solver->assert_expr(m.mk_not(lit)); + lbool is_sat = m_solver->check_sat(); + if (is_sat != l_false) { + std::cout << "did not verify: " << is_sat << " " << clause << "\n\n"; + m_solver->display(std::cout); + if (is_sat == l_true) { + model_ref mdl; + m_solver->get_model(mdl); + std::cout << *mdl << "\n"; + } + exit(0); + } + m_solver->pop(1); + std::cout << "(verified-smt)\n"; + if (proof_hint) + std::cout << "(missed-hint " << mk_pp(proof_hint, m) << ")\n"; + add_clause(clause); + } + + void assume(expr_ref_vector const& clause) { + add_clause(clause); + m_solver->assert_expr(mk_or(clause)); + } +}; + +class proof_cmds_imp : public proof_cmds { + ast_manager& m; + expr_ref_vector m_lits; + app_ref m_proof_hint; + smt_checker m_checker; +public: + proof_cmds_imp(ast_manager& m): m(m), m_lits(m), m_proof_hint(m), m_checker(m) {} + + void add_literal(expr* e) override { + if (m.is_proof(e)) + m_proof_hint = to_app(e); + else + m_lits.push_back(e); + } + + void end_assumption() override { + m_checker.assume(m_lits); + m_lits.reset(); + m_proof_hint.reset(); + } + + void end_learned() override { + m_checker.check(m_lits, m_proof_hint); + m_lits.reset(); + m_proof_hint.reset(); + } + + void end_deleted() override { + m_lits.reset(); + m_proof_hint.reset(); + } +}; + + +static proof_cmds& get(cmd_context& ctx) { + if (!ctx.get_proof_cmds()) + ctx.set_proof_cmds(alloc(proof_cmds_imp, ctx.m())); + return *ctx.get_proof_cmds(); +} + +// assumption +class assume_cmd : public cmd { +public: + assume_cmd():cmd("assume") {} + char const* get_usage() const override { return "+"; } + char const * get_descr(cmd_context& ctx) const override { return "proof command for adding assumption (input assertion)"; } + unsigned get_arity() const override { return VAR_ARITY; } + void prepare(cmd_context & ctx) override {} + void finalize(cmd_context & ctx) override {} + void failure_cleanup(cmd_context & ctx) override {} + cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; } + void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); } + void execute(cmd_context& ctx) override { get(ctx).end_assumption(); } +}; + +// deleted clause +class del_cmd : public cmd { +public: + del_cmd():cmd("del") {} + char const* get_usage() const override { return "+"; } + char const * get_descr(cmd_context& ctx) const override { return "proof command for clause deletion"; } + unsigned get_arity() const override { return VAR_ARITY; } + void prepare(cmd_context & ctx) override {} + void finalize(cmd_context & ctx) override {} + void failure_cleanup(cmd_context & ctx) override {} + cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; } + void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); } + void execute(cmd_context& ctx) override { get(ctx).end_deleted(); } +}; + +// learned/redundant clause +class learn_cmd : public cmd { +public: + learn_cmd():cmd("learn") {} + char const* get_usage() const override { return "+"; } + char const* get_descr(cmd_context& ctx) const override { return "proof command for learned (redundant) clauses"; } + unsigned get_arity() const override { return VAR_ARITY; } + void prepare(cmd_context & ctx) override {} + void finalize(cmd_context & ctx) override {} + void failure_cleanup(cmd_context & ctx) override {} + cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; } + void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); } + void execute(cmd_context& ctx) override { get(ctx).end_learned(); } +}; + +void install_proof_cmds(cmd_context & ctx) { + ctx.insert(alloc(del_cmd)); + ctx.insert(alloc(learn_cmd)); + ctx.insert(alloc(assume_cmd)); +} diff --git a/src/cmd_context/extra_cmds/proof_cmds.h b/src/cmd_context/extra_cmds/proof_cmds.h new file mode 100644 index 000000000..9625e93ad --- /dev/null +++ b/src/cmd_context/extra_cmds/proof_cmds.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + proof_cmds.h + +Abstract: + Commands for reading proofs. + +Author: + + Nikolaj Bjorner (nbjorner) 2022-8-26 + +Notes: + +--*/ +#pragma once + +/** + proof_cmds is a structure that tracks an evidence trail. + + The main interface is to: + add literals one by one, + add proof hints + until receiving end-command: assumption, learned, deleted. + Evidence can be checked: + - By DRUP + - Theory lemmas + +*/ + + +class cmd_context; +void install_proof_cmds(cmd_context & ctx); + diff --git a/src/cmd_context/pdecl.cpp b/src/cmd_context/pdecl.cpp index 5c7058bb5..b8dd01aea 100644 --- a/src/cmd_context/pdecl.cpp +++ b/src/cmd_context/pdecl.cpp @@ -781,7 +781,7 @@ struct pdecl_manager::sort_info { m_decl(d) { m.inc_ref(d); } - virtual ~sort_info() {} + virtual ~sort_info() = default; virtual unsigned obj_size() const { return sizeof(sort_info); } virtual void finalize(pdecl_manager & m) { m.dec_ref(m_decl); } virtual void display(std::ostream & out, pdecl_manager const & m) const = 0; diff --git a/src/cmd_context/pdecl.h b/src/cmd_context/pdecl.h index 66e1e5e81..a55f782f0 100644 --- a/src/cmd_context/pdecl.h +++ b/src/cmd_context/pdecl.h @@ -38,7 +38,7 @@ protected: virtual size_t obj_size() const { UNREACHABLE(); return sizeof(*this); } pdecl(unsigned id, unsigned num_params):m_id(id), m_num_params(num_params), m_ref_count(0) {} virtual void finalize(pdecl_manager & m) {} - virtual ~pdecl() {} + virtual ~pdecl() = default; public: virtual bool check_num_params(pdecl * other) const { return m_num_params == other->m_num_params; } unsigned get_num_params() const { return m_num_params; } @@ -257,7 +257,7 @@ public: class new_datatype_eh { public: - virtual ~new_datatype_eh() {} + virtual ~new_datatype_eh() = default; virtual void operator()(sort * dt, pdecl* pd) = 0; }; diff --git a/src/math/automata/boolean_algebra.h b/src/math/automata/boolean_algebra.h index 8c1ca55e6..a642ceb41 100644 --- a/src/math/automata/boolean_algebra.h +++ b/src/math/automata/boolean_algebra.h @@ -25,7 +25,7 @@ Revision History: template class positive_boolean_algebra { public: - virtual ~positive_boolean_algebra() {} + virtual ~positive_boolean_algebra() = default; virtual T mk_false() = 0; virtual T mk_true() = 0; virtual T mk_and(T x, T y) = 0; diff --git a/src/math/grobner/pdd_simplifier.cpp b/src/math/grobner/pdd_simplifier.cpp index e8b10ad80..4a8140681 100644 --- a/src/math/grobner/pdd_simplifier.cpp +++ b/src/math/grobner/pdd_simplifier.cpp @@ -19,7 +19,7 @@ Extended Linear Simplification (as exploited in Bosphorus AAAI 2019): - multiply each polynomial by one variable from their orbits. - - The orbit of a varible are the variables that occur in the same monomial as it in some polynomial. + - The orbit of a variable are the variables that occur in the same monomial as it in some polynomial. - The extended set of polynomials is fed to a linear Gauss Jordan Eliminator that extracts additional linear equalities. - Bosphorus uses M4RI to perform efficient GJE to scale on large bit-matrices. @@ -32,7 +32,7 @@ The method seems rather specific to hardware multipliers so not clear it is useful to generalize. - find monomials that contain pairs of vanishing polynomials, transitively - withtout actually inlining. + without actually inlining. Then color polynomial variables w by p, resp, q if they occur in polynomial equalities w - r = 0, such that all paths in r contain a node colored by p, resp q. polynomial variables that get colored by both p and q can be set to 0. diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index aefe5d6dc..63c5ad835 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -61,8 +61,7 @@ namespace dd { solver::solver(reslimit& lim, pdd_manager& m) : m(m), - m_limit(lim), - m_conflict(nullptr) + m_limit(lim) {} solver::~solver() { @@ -179,9 +178,8 @@ namespace dd { set[i]->set_index(j++); } ~scoped_update() { - for (; i < sz; ++i) { + for (; i < sz; ++i) nextj(); - } set.shrink(j); } }; @@ -191,23 +189,24 @@ namespace dd { equation& target = *set[sr.i]; bool changed_leading_term = false; bool simplified = true; - simplified = !done() && simplifier(target, changed_leading_term); + simplified = !done() && simplifier(target, changed_leading_term); + - if (simplified && is_trivial(target)) { + if (simplified && is_trivial(target)) retire(&target); - } else if (simplified && check_conflict(target)) { // pushed to solved } else if (simplified && changed_leading_term) { - push_equation(to_simplify, target); - if (!m_var2level.empty()) { + if (&m_to_simplify == &set) + sr.nextj(); + else + push_equation(to_simplify, target); + if (!m_var2level.empty()) m_levelp1 = std::max(m_var2level[target.poly().var()]+1, m_levelp1); - } } - else { + else sr.nextj(); - } } } diff --git a/src/math/grobner/pdd_solver.h b/src/math/grobner/pdd_solver.h index 0069eadf3..40f8fdce2 100644 --- a/src/math/grobner/pdd_solver.h +++ b/src/math/grobner/pdd_solver.h @@ -121,7 +121,7 @@ private: vector> m_subst; mutable u_dependency_manager m_dep_manager; equation_vector m_all_eqs; - equation* m_conflict; + equation* m_conflict = nullptr; bool m_too_complex; public: solver(reslimit& lim, pdd_manager& m); diff --git a/src/math/hilbert/heap_trie.h b/src/math/hilbert/heap_trie.h index fcdce7311..b492fb7ee 100644 --- a/src/math/hilbert/heap_trie.h +++ b/src/math/hilbert/heap_trie.h @@ -66,7 +66,7 @@ class heap_trie { unsigned m_ref; public: node(node_t t): m_type(t), m_ref(0) {} - virtual ~node() {} + virtual ~node() = default; node_t type() const { return m_type; } void inc_ref() { ++m_ref; } void dec_ref() { SASSERT(m_ref > 0); --m_ref; } diff --git a/src/math/interval/dep_intervals.cpp b/src/math/interval/dep_intervals.cpp index 7d543b0fb..b1d18092c 100644 --- a/src/math/interval/dep_intervals.cpp +++ b/src/math/interval/dep_intervals.cpp @@ -7,7 +7,7 @@ Abstract: - intervals with depedency tracking. + intervals with dependency tracking. Author: Nikolaj Bjorner (nbjorner) diff --git a/src/math/interval/dep_intervals.h b/src/math/interval/dep_intervals.h index 990816696..d14a0fc53 100644 --- a/src/math/interval/dep_intervals.h +++ b/src/math/interval/dep_intervals.h @@ -7,7 +7,7 @@ Abstract: - intervals with depedency tracking. + intervals with dependency tracking. Author: Nikolaj Bjorner (nbjorner) diff --git a/src/math/lp/emonics.cpp b/src/math/lp/emonics.cpp index 5b3ae64f0..060c2b1b9 100644 --- a/src/math/lp/emonics.cpp +++ b/src/math/lp/emonics.cpp @@ -518,7 +518,7 @@ std::ostream& emonics::display(std::ostream& out, cell* c) const { bool emonics::invariant() const { TRACE("nla_solver_mons", display(tout);); - // the varible index contains exactly the active monomials + // the variable index contains exactly the active monomials unsigned mons = 0; for (lpvar v = 0; v < m_var2index.size(); v++) { if (is_monic_var(v)) { @@ -546,6 +546,7 @@ bool emonics::invariant() const { } CTRACE("nla_solver_mons", !found, tout << "not found v" << v << ": " << m << "\n";); SASSERT(found); + (void)found; c = c->m_next; } while (c != ht.m_head); diff --git a/src/math/lp/lar_constraints.h b/src/math/lp/lar_constraints.h index 3bcd62d00..8e6311683 100644 --- a/src/math/lp/lar_constraints.h +++ b/src/math/lp/lar_constraints.h @@ -57,7 +57,7 @@ public: virtual vector> coeffs() const = 0; lar_base_constraint(unsigned j, lconstraint_kind kind, const mpq& right_side) :m_kind(kind), m_right_side(right_side), m_active(false), m_j(j) {} - virtual ~lar_base_constraint() {} + virtual ~lar_base_constraint() = default; lconstraint_kind kind() const { return m_kind; } mpq const& rhs() const { return m_right_side; } diff --git a/src/math/lp/lar_solver.h b/src/math/lp/lar_solver.h index cce63855d..e7f8d0ea4 100644 --- a/src/math/lp/lar_solver.h +++ b/src/math/lp/lar_solver.h @@ -93,7 +93,7 @@ class lar_solver : public column_namer { unsigned_vector m_row_bounds_to_replay; u_set m_basic_columns_with_changed_cost; - // these are basic columns with the value changed, so the the corresponding row in the tableau + // these are basic columns with the value changed, so the corresponding row in the tableau // does not sum to zero anymore u_set m_incorrect_columns; // copy of m_r_solver.inf_set() diff --git a/src/math/lp/lp_api.h b/src/math/lp/lp_api.h index 8c05f8354..2a4e5058d 100644 --- a/src/math/lp/lp_api.h +++ b/src/math/lp/lp_api.h @@ -49,7 +49,7 @@ namespace lp_api { m_constraints[1] = ct; } - virtual ~bound() {} + virtual ~bound() = default; theory_var get_var() const { return m_var; } diff --git a/src/math/lp/lp_settings_def.h b/src/math/lp/lp_settings_def.h index 2aba35946..58b37a19d 100644 --- a/src/math/lp/lp_settings_def.h +++ b/src/math/lp/lp_settings_def.h @@ -49,6 +49,7 @@ const char* lp_status_to_string(lp_status status) { case lp_status::TIME_EXHAUSTED: return "TIME_EXHAUSTED"; case lp_status::EMPTY: return "EMPTY"; case lp_status::UNSTABLE: return "UNSTABLE"; + case lp_status::CANCELLED: return "CANCELLED"; default: lp_unreachable(); } diff --git a/src/math/lp/matrix.h b/src/math/lp/matrix.h index 80ddb1228..88a405614 100644 --- a/src/math/lp/matrix.h +++ b/src/math/lp/matrix.h @@ -22,7 +22,7 @@ public: virtual void set_number_of_rows(unsigned m) = 0; virtual void set_number_of_columns(unsigned n) = 0; - virtual ~matrix() {} + virtual ~matrix() = default; bool is_equal(const matrix& other); bool operator == (matrix const & other) { diff --git a/src/math/lp/mps_reader.h b/src/math/lp/mps_reader.h index f0fa074cc..f2cf2d320 100644 --- a/src/math/lp/mps_reader.h +++ b/src/math/lp/mps_reader.h @@ -20,7 +20,7 @@ Revision History: #pragma once -// reads an MPS file reperesenting a Mixed Integer Program +// reads an MPS file representing a Mixed Integer Program #include #include #include diff --git a/src/math/lp/nex.h b/src/math/lp/nex.h index 083b935a4..e4087a407 100644 --- a/src/math/lp/nex.h +++ b/src/math/lp/nex.h @@ -85,7 +85,7 @@ public: bool is_scalar() const { return type() == expr_type::SCALAR; } virtual bool is_pure_monomial() const { return false; } std::string str() const { std::stringstream ss; print(ss); return ss.str(); } - virtual ~nex() {} + virtual ~nex() = default; virtual bool contains(lpvar j) const { return false; } virtual unsigned get_degree() const = 0; // simplifies the expression and also assigns the address of "this" to *e diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index 51604fb47..974c48d14 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -45,14 +45,19 @@ namespace nla { if (is_conflicting()) return; - if (propagate_bounds()) - return; + try { + if (propagate_bounds()) + return; - if (propagate_eqs()) - return; + if (propagate_eqs()) + return; - if (propagate_factorization()) - return; + if (propagate_factorization()) + return; + } + catch (...) { + + } if (quota > 1) quota--; diff --git a/src/math/lp/tail_matrix.h b/src/math/lp/tail_matrix.h index 2047e8c7e..9fa1a4a47 100644 --- a/src/math/lp/tail_matrix.h +++ b/src/math/lp/tail_matrix.h @@ -37,7 +37,7 @@ public: virtual void apply_from_left(vector & w, lp_settings & settings) = 0; virtual void apply_from_right(vector & w) = 0; virtual void apply_from_right(indexed_vector & w) = 0; - virtual ~tail_matrix() {} + virtual ~tail_matrix() = default; virtual bool is_dense() const = 0; struct ref_row { const tail_matrix & m_A; diff --git a/src/math/simplex/model_based_opt.cpp b/src/math/simplex/model_based_opt.cpp index 8b8f82a31..b90851582 100644 --- a/src/math/simplex/model_based_opt.cpp +++ b/src/math/simplex/model_based_opt.cpp @@ -1,1300 +1,1638 @@ -/*++ -Copyright (c) 2016 Microsoft Corporation - -Module Name: - - model_based_opt.cpp - -Abstract: - - Model-based optimization and projection for linear real, integer arithmetic. - -Author: - - Nikolaj Bjorner (nbjorner) 2016-27-4 - -Revision History: - - ---*/ - -#include "math/simplex/model_based_opt.h" -#include "util/uint_set.h" -#include "util/z3_exception.h" - -std::ostream& operator<<(std::ostream& out, opt::ineq_type ie) { - switch (ie) { - case opt::t_eq: return out << " = "; - case opt::t_lt: return out << " < "; - case opt::t_le: return out << " <= "; - case opt::t_mod: return out << " mod "; - } - return out; -} - - -namespace opt { - - /** - * Convert a row ax + coeffs + coeff = value into a definition for x - * x = (value - coeffs - coeff)/a - * as backdrop we have existing assignments to x and other variables that - * satisfy the equality with value, and such that value satisfies - * the row constraint ( = , <= , < , mod) - */ - model_based_opt::def::def(row const& r, unsigned x) { - for (var const & v : r.m_vars) { - if (v.m_id != x) { - m_vars.push_back(v); - } - else { - m_div = -v.m_coeff; - } - } - m_coeff = r.m_coeff; - switch (r.m_type) { - case opt::t_lt: - m_coeff += m_div; - break; - case opt::t_le: - // for: ax >= t, then x := (t + a - 1) div a - if (m_div.is_pos()) { - m_coeff += m_div; - m_coeff -= rational::one(); - } - break; - default: - break; - } - normalize(); - SASSERT(m_div.is_pos()); - } - - model_based_opt::def model_based_opt::def::operator+(def const& other) const { - def result; - vector const& vs1 = m_vars; - vector const& vs2 = other.m_vars; - vector & vs = result.m_vars; - rational c1(1), c2(1); - if (m_div != other.m_div) { - c1 = other.m_div; - c2 = m_div; - } - unsigned i = 0, j = 0; - while (i < vs1.size() || j < vs2.size()) { - unsigned v1 = UINT_MAX, v2 = UINT_MAX; - if (i < vs1.size()) v1 = vs1[i].m_id; - if (j < vs2.size()) v2 = vs2[j].m_id; - if (v1 == v2) { - vs.push_back(vs1[i]); - vs.back().m_coeff *= c1; - vs.back().m_coeff += c2 * vs2[j].m_coeff; - ++i; ++j; - if (vs.back().m_coeff.is_zero()) { - vs.pop_back(); - } - } - else if (v1 < v2) { - vs.push_back(vs1[i]); - vs.back().m_coeff *= c1; - } - else { - vs.push_back(vs2[j]); - vs.back().m_coeff *= c2; - } - } - result.m_div = c1*m_div; - result.m_coeff = (m_coeff*c1) + (other.m_coeff*c2); - result.normalize(); - return result; - } - - model_based_opt::def model_based_opt::def::operator/(rational const& r) const { - def result(*this); - result.m_div *= r; - result.normalize(); - return result; - } - - model_based_opt::def model_based_opt::def::operator*(rational const& n) const { - def result(*this); - for (var& v : result.m_vars) { - v.m_coeff *= n; - } - result.m_coeff *= n; - result.normalize(); - return result; - } - - model_based_opt::def model_based_opt::def::operator+(rational const& n) const { - def result(*this); - result.m_coeff += n * result.m_div; - result.normalize(); - return result; - } - - void model_based_opt::def::normalize() { - if (!m_div.is_int()) { - rational den = denominator(m_div); - SASSERT(den > 1); - for (var& v : m_vars) - v.m_coeff *= den; - m_coeff *= den; - m_div *= den; - - } - if (m_div.is_neg()) { - for (var& v : m_vars) - v.m_coeff.neg(); - m_coeff.neg(); - m_div.neg(); - } - if (m_div.is_one()) - return; - rational g(m_div); - if (!m_coeff.is_int()) - return; - g = gcd(g, m_coeff); - for (var const& v : m_vars) { - if (!v.m_coeff.is_int()) - return; - g = gcd(g, abs(v.m_coeff)); - if (g.is_one()) - break; - } - if (!g.is_one()) { - for (var& v : m_vars) - v.m_coeff /= g; - m_coeff /= g; - m_div /= g; - } - } - - model_based_opt::model_based_opt() { - m_rows.push_back(row()); - } - - bool model_based_opt::invariant() { - for (unsigned i = 0; i < m_rows.size(); ++i) { - if (!invariant(i, m_rows[i])) { - return false; - } - } - return true; - } - -#define PASSERT(_e_) { CTRACE("qe", !(_e_), display(tout, r); display(tout);); SASSERT(_e_); } - - bool model_based_opt::invariant(unsigned index, row const& r) { - vector const& vars = r.m_vars; - for (unsigned i = 0; i < vars.size(); ++i) { - // variables in each row are sorted and have non-zero coefficients - PASSERT(i + 1 == vars.size() || vars[i].m_id < vars[i+1].m_id); - PASSERT(!vars[i].m_coeff.is_zero()); - PASSERT(index == 0 || m_var2row_ids[vars[i].m_id].contains(index)); - } - - PASSERT(r.m_value == eval(r)); - PASSERT(r.m_type != t_eq || r.m_value.is_zero()); - // values satisfy constraints - PASSERT(index == 0 || r.m_type != t_lt || r.m_value.is_neg()); - PASSERT(index == 0 || r.m_type != t_le || !r.m_value.is_pos()); - PASSERT(index == 0 || r.m_type != t_mod || (mod(r.m_value, r.m_mod).is_zero())); - return true; - } - - // a1*x + obj - // a2*x + t2 <= 0 - // a3*x + t3 <= 0 - // a4*x + t4 <= 0 - // a1 > 0, a2 > 0, a3 > 0, a4 < 0 - // x <= -t2/a2 - // x <= -t2/a3 - // determine lub among these. - // then resolve lub with others - // e.g., -t2/a2 <= -t3/a3, then - // replace inequality a3*x + t3 <= 0 by -t2/a2 + t3/a3 <= 0 - // mark a4 as invalid. - // - - // a1 < 0, a2 < 0, a3 < 0, a4 > 0 - // x >= t2/a2 - // x >= t3/a3 - // determine glb among these - // the resolve glb with others. - // e.g. t2/a2 >= t3/a3 - // then replace a3*x + t3 by t3/a3 - t2/a2 <= 0 - // - inf_eps model_based_opt::maximize() { - SASSERT(invariant()); - unsigned_vector bound_trail, bound_vars; - TRACE("opt", display(tout << "tableau\n");); - while (!objective().m_vars.empty()) { - var v = objective().m_vars.back(); - unsigned x = v.m_id; - rational const& coeff = v.m_coeff; - unsigned bound_row_index; - rational bound_coeff; - if (find_bound(x, bound_row_index, bound_coeff, coeff.is_pos())) { - SASSERT(!bound_coeff.is_zero()); - TRACE("opt", display(tout << "update: " << v << " ", objective()); - for (unsigned above : m_above) { - display(tout << "resolve: ", m_rows[above]); - }); - for (unsigned above : m_above) { - resolve(bound_row_index, bound_coeff, above, x); - } - for (unsigned below : m_below) { - resolve(bound_row_index, bound_coeff, below, x); - } - // coeff*x + objective <= ub - // a2*x + t2 <= 0 - // => coeff*x <= -t2*coeff/a2 - // objective + t2*coeff/a2 <= ub - - mul_add(false, m_objective_id, - coeff/bound_coeff, bound_row_index); - retire_row(bound_row_index); - bound_trail.push_back(bound_row_index); - bound_vars.push_back(x); - } - else { - TRACE("opt", display(tout << "unbound: " << v << " ", objective());); - update_values(bound_vars, bound_trail); - return inf_eps::infinity(); - } - } - - // - // update the evaluation of variables to satisfy the bound. - // - - update_values(bound_vars, bound_trail); - - rational value = objective().m_value; - if (objective().m_type == t_lt) { - return inf_eps(inf_rational(value, rational(-1))); - } - else { - return inf_eps(inf_rational(value)); - } - } - - - void model_based_opt::update_value(unsigned x, rational const& val) { - rational old_val = m_var2value[x]; - m_var2value[x] = val; - SASSERT(val.is_int() || !is_int(x)); - unsigned_vector const& row_ids = m_var2row_ids[x]; - for (unsigned row_id : row_ids) { - rational coeff = get_coefficient(row_id, x); - if (coeff.is_zero()) { - continue; - } - row & r = m_rows[row_id]; - rational delta = coeff * (val - old_val); - r.m_value += delta; - SASSERT(invariant(row_id, r)); - } - } - - - void model_based_opt::update_values(unsigned_vector const& bound_vars, unsigned_vector const& bound_trail) { - for (unsigned i = bound_trail.size(); i-- > 0; ) { - unsigned x = bound_vars[i]; - row& r = m_rows[bound_trail[i]]; - rational val = r.m_coeff; - rational old_x_val = m_var2value[x]; - rational new_x_val; - rational x_coeff, eps(0); - vector const& vars = r.m_vars; - for (var const& v : vars) { - if (x == v.m_id) { - x_coeff = v.m_coeff; - } - else { - val += m_var2value[v.m_id]*v.m_coeff; - } - } - SASSERT(!x_coeff.is_zero()); - new_x_val = -val/x_coeff; - - if (r.m_type == t_lt) { - eps = abs(old_x_val - new_x_val)/rational(2); - eps = std::min(rational::one(), eps); - SASSERT(!eps.is_zero()); - - // - // ax + t < 0 - // <=> x < -t/a - // <=> x := -t/a - epsilon - // - if (x_coeff.is_pos()) { - new_x_val -= eps; - } - // - // -ax + t < 0 - // <=> -ax < -t - // <=> -x < -t/a - // <=> x > t/a - // <=> x := t/a + epsilon - // - else { - new_x_val += eps; - } - } - TRACE("opt", display(tout << "v" << x - << " coeff_x: " << x_coeff - << " old_x_val: " << old_x_val - << " new_x_val: " << new_x_val - << " eps: " << eps << " ", r); ); - m_var2value[x] = new_x_val; - - r.m_value = eval(r); - SASSERT(invariant(bound_trail[i], r)); - } - - // update and check bounds for all other affected rows. - for (unsigned i = bound_trail.size(); i-- > 0; ) { - unsigned x = bound_vars[i]; - unsigned_vector const& row_ids = m_var2row_ids[x]; - for (unsigned row_id : row_ids) { - row & r = m_rows[row_id]; - r.m_value = eval(r); - SASSERT(invariant(row_id, r)); - } - } - SASSERT(invariant()); - } - - bool model_based_opt::find_bound(unsigned x, unsigned& bound_row_index, rational& bound_coeff, bool is_pos) { - bound_row_index = UINT_MAX; - rational lub_val; - rational const& x_val = m_var2value[x]; - unsigned_vector const& row_ids = m_var2row_ids[x]; - uint_set visited; - m_above.reset(); - m_below.reset(); - for (unsigned row_id : row_ids) { - SASSERT(row_id != m_objective_id); - if (visited.contains(row_id)) { - continue; - } - visited.insert(row_id); - row& r = m_rows[row_id]; - if (r.m_alive) { - rational a = get_coefficient(row_id, x); - if (a.is_zero()) { - // skip - } - else if (a.is_pos() == is_pos || r.m_type == t_eq) { - rational value = x_val - (r.m_value/a); - if (bound_row_index == UINT_MAX) { - lub_val = value; - bound_row_index = row_id; - bound_coeff = a; - } - else if ((value == lub_val && r.m_type == opt::t_lt) || - (is_pos && value < lub_val) || - - (!is_pos && value > lub_val)) { - m_above.push_back(bound_row_index); - lub_val = value; - bound_row_index = row_id; - bound_coeff = a; - } - else { - m_above.push_back(row_id); - } - } - else { - m_below.push_back(row_id); - } - } - } - return bound_row_index != UINT_MAX; - } - - void model_based_opt::retire_row(unsigned row_id) { - m_rows[row_id].m_alive = false; - m_retired_rows.push_back(row_id); - } - - rational model_based_opt::eval(unsigned x) const { - return m_var2value[x]; - } - - rational model_based_opt::eval(def const& d) const { - vector const& vars = d.m_vars; - rational val = d.m_coeff; - for (var const& v : vars) { - val += v.m_coeff * eval(v.m_id); - } - val /= d.m_div; - return val; - } - - rational model_based_opt::eval(row const& r) const { - vector const& vars = r.m_vars; - rational val = r.m_coeff; - for (var const& v : vars) { - val += v.m_coeff * eval(v.m_id); - } - return val; - } - - rational model_based_opt::eval(vector const& coeffs) const { - rational val(0); - for (var const& v : coeffs) - val += v.m_coeff * eval(v.m_id); - return val; - } - - rational model_based_opt::get_coefficient(unsigned row_id, unsigned var_id) const { - return m_rows[row_id].get_coefficient(var_id); - } - - rational model_based_opt::row::get_coefficient(unsigned var_id) const { - if (m_vars.empty()) { - return rational::zero(); - } - unsigned lo = 0, hi = m_vars.size(); - while (lo < hi) { - unsigned mid = lo + (hi - lo)/2; - SASSERT(mid < hi); - unsigned id = m_vars[mid].m_id; - if (id == var_id) { - lo = mid; - break; - } - if (id < var_id) { - lo = mid + 1; - } - else { - hi = mid; - } - } - if (lo == m_vars.size()) { - return rational::zero(); - } - unsigned id = m_vars[lo].m_id; - if (id == var_id) { - return m_vars[lo].m_coeff; - } - else { - return rational::zero(); - } - } - - model_based_opt::row& model_based_opt::row::normalize() { -#if 0 - if (m_type == t_mod) - return *this; - rational D(denominator(abs(m_coeff))); - if (D == 0) - D = 1; - for (auto const& [id, coeff] : m_vars) - if (coeff != 0) - D = lcm(D, denominator(abs(coeff))); - if (D == 1) - return *this; - SASSERT(D > 0); - for (auto & [id, coeff] : m_vars) - coeff *= D; - m_coeff *= D; -#endif - return *this; - } - - // - // Let - // row1: t1 + a1*x <= 0 - // row2: t2 + a2*x <= 0 - // - // assume a1, a2 have the same signs: - // (t2 + a2*x) <= (t1 + a1*x)*a2/a1 - // <=> t2*a1/a2 - t1 <= 0 - // <=> t2 - t1*a2/a1 <= 0 - // - // assume a1 > 0, -a2 < 0: - // t1 + a1*x <= 0, t2 - a2*x <= 0 - // t2/a2 <= -t1/a1 - // t2 + t1*a2/a1 <= 0 - // assume -a1 < 0, a2 > 0: - // t1 - a1*x <= 0, t2 + a2*x <= 0 - // t1/a1 <= -t2/a2 - // t2 + t1*a2/a1 <= 0 - // - // the resolvent is the same in all cases (simpler proof should exist) - // - - void model_based_opt::resolve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x) { - - SASSERT(a1 == get_coefficient(row_src, x)); - SASSERT(!a1.is_zero()); - SASSERT(row_src != row_dst); - - if (m_rows[row_dst].m_alive) { - rational a2 = get_coefficient(row_dst, x); - if (is_int(x)) { - TRACE("opt", - tout << a1 << " " << a2 << ": "; - display(tout, m_rows[row_dst]); - display(tout, m_rows[row_src]);); - if (a1.is_pos() != a2.is_pos() || m_rows[row_src].m_type == opt::t_eq) { - mul_add(x, a1, row_src, a2, row_dst); - } - else { - mul(row_dst, abs(a1)); - mul_add(false, row_dst, -abs(a2), row_src); - } - TRACE("opt", display(tout, m_rows[row_dst]);); - normalize(row_dst); - } - else { - mul_add(row_dst != m_objective_id && a1.is_pos() == a2.is_pos(), row_dst, -a2/a1, row_src); - } - } - } - - /** - * a1 > 0 - * a1*x + r1 = value - * a2*x + r2 <= 0 - * ------------------ - * a1*r2 - a2*r1 <= value - */ - void model_based_opt::solve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x) { - SASSERT(a1 == get_coefficient(row_src, x)); - SASSERT(a1.is_pos()); - SASSERT(row_src != row_dst); - if (!m_rows[row_dst].m_alive) return; - rational a2 = get_coefficient(row_dst, x); - mul(row_dst, a1); - mul_add(false, row_dst, -a2, row_src); - SASSERT(get_coefficient(row_dst, x).is_zero()); - } - - // resolution for integer rows. - void model_based_opt::mul_add( - unsigned x, rational const& src_c, unsigned row_src, rational const& dst_c, unsigned row_dst) { - row& dst = m_rows[row_dst]; - row const& src = m_rows[row_src]; - SASSERT(is_int(x)); - SASSERT(t_le == dst.m_type && t_le == src.m_type); - SASSERT(src_c.is_int()); - SASSERT(dst_c.is_int()); - SASSERT(m_var2value[x].is_int()); - - rational abs_src_c = abs(src_c); - rational abs_dst_c = abs(dst_c); - rational x_val = m_var2value[x]; - rational slack = (abs_src_c - rational::one()) * (abs_dst_c - rational::one()); - rational dst_val = dst.m_value - x_val*dst_c; - rational src_val = src.m_value - x_val*src_c; - rational distance = abs_src_c * dst_val + abs_dst_c * src_val + slack; - bool use_case1 = distance.is_nonpos() || abs_src_c.is_one() || abs_dst_c.is_one(); - -#if 0 - if (distance.is_nonpos() && !abs_src_c.is_one() && !abs_dst_c.is_one()) { - unsigned r = copy_row(row_src); - mul_add(false, r, rational::one(), row_dst); - del_var(r, x); - add(r, slack); - TRACE("qe", tout << m_rows[r];); - SASSERT(!m_rows[r].m_value.is_pos()); - } -#endif - - if (use_case1) { - TRACE("opt", tout << "slack: " << slack << " " << src_c << " " << dst_val << " " << dst_c << " " << src_val << "\n";); - // dst <- abs_src_c*dst + abs_dst_c*src + slack - mul(row_dst, abs_src_c); - add(row_dst, slack); - mul_add(false, row_dst, abs_dst_c, row_src); - return; - } - - // - // create finite disjunction for |b|. - // exists x, z in [0 .. |b|-2] . b*x + s + z = 0 && ax + t <= 0 && bx + s <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && ax + t <= 0 && bx + s <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && bx + s <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && -z - s + s <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && -z <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 - // <=> - // exists x, z in [0 .. |b|-2] . b*x = -z - s && a*n_sign(b)(s + z) + |b|t <= 0 - // <=> - // exists z in [0 .. |b|-2] . |b| | (z + s) && a*n_sign(b)(s + z) + |b|t <= 0 - // - - TRACE("qe", tout << "finite disjunction " << distance << " " << src_c << " " << dst_c << "\n";); - vector coeffs; - if (abs_dst_c <= abs_src_c) { - rational z = mod(dst_val, abs_dst_c); - if (!z.is_zero()) z = abs_dst_c - z; - mk_coeffs_without(coeffs, dst.m_vars, x); - add_divides(coeffs, dst.m_coeff + z, abs_dst_c); - add(row_dst, z); - mul(row_dst, src_c * n_sign(dst_c)); - mul_add(false, row_dst, abs_dst_c, row_src); - } - else { - // z := b - (s + bx) mod b - // := b - s mod b - // b | s + z <=> b | s + b - s mod b <=> b | s - s mod b - rational z = mod(src_val, abs_src_c); - if (!z.is_zero()) z = abs_src_c - z; - mk_coeffs_without(coeffs, src.m_vars, x); - add_divides(coeffs, src.m_coeff + z, abs_src_c); - mul(row_dst, abs_src_c); - add(row_dst, z * dst_c * n_sign(src_c)); - mul_add(false, row_dst, dst_c * n_sign(src_c), row_src); - } - } - - void model_based_opt::mk_coeffs_without(vector& dst, vector const& src, unsigned x) { - for (var const & v : src) { - if (v.m_id != x) dst.push_back(v); - } - } - - rational model_based_opt::n_sign(rational const& b) const { - return rational(b.is_pos()?-1:1); - } - - void model_based_opt::mul(unsigned dst, rational const& c) { - if (c.is_one()) return; - row& r = m_rows[dst]; - for (auto & v : r.m_vars) { - v.m_coeff *= c; - } - r.m_coeff *= c; - r.m_value *= c; - } - - void model_based_opt::add(unsigned dst, rational const& c) { - row& r = m_rows[dst]; - r.m_coeff += c; - r.m_value += c; - } - - void model_based_opt::sub(unsigned dst, rational const& c) { - row& r = m_rows[dst]; - r.m_coeff -= c; - r.m_value -= c; - } - - void model_based_opt::del_var(unsigned dst, unsigned x) { - row& r = m_rows[dst]; - unsigned j = 0; - for (var & v : r.m_vars) { - if (v.m_id == x) { - r.m_value -= eval(x)*r.m_coeff; - } - else { - r.m_vars[j++] = v; - } - } - r.m_vars.shrink(j); - } - - - void model_based_opt::normalize(unsigned row_id) { - row& r = m_rows[row_id]; - if (r.m_vars.empty()) { - retire_row(row_id); - return; - } - if (r.m_type == t_mod) return; - rational g(abs(r.m_vars[0].m_coeff)); - bool all_int = g.is_int(); - for (unsigned i = 1; all_int && !g.is_one() && i < r.m_vars.size(); ++i) { - rational const& coeff = r.m_vars[i].m_coeff; - if (coeff.is_int()) { - g = gcd(g, abs(coeff)); - } - else { - all_int = false; - } - } - if (all_int && !r.m_coeff.is_zero()) { - if (r.m_coeff.is_int()) { - g = gcd(g, abs(r.m_coeff)); - } - else { - all_int = false; - } - } - if (all_int && !g.is_one()) { - SASSERT(!g.is_zero()); - mul(row_id, rational::one()/g); - } - } - - // - // set row1 <- row1 + c*row2 - // - void model_based_opt::mul_add(bool same_sign, unsigned row_id1, rational const& c, unsigned row_id2) { - if (c.is_zero()) { - return; - } - - m_new_vars.reset(); - row& r1 = m_rows[row_id1]; - row const& r2 = m_rows[row_id2]; - unsigned i = 0, j = 0; - while (i < r1.m_vars.size() || j < r2.m_vars.size()) { - if (j == r2.m_vars.size()) { - m_new_vars.append(r1.m_vars.size() - i, r1.m_vars.data() + i); - break; - } - if (i == r1.m_vars.size()) { - for (; j < r2.m_vars.size(); ++j) { - m_new_vars.push_back(r2.m_vars[j]); - m_new_vars.back().m_coeff *= c; - if (row_id1 != m_objective_id) { - m_var2row_ids[r2.m_vars[j].m_id].push_back(row_id1); - } - } - break; - } - - unsigned v1 = r1.m_vars[i].m_id; - unsigned v2 = r2.m_vars[j].m_id; - if (v1 == v2) { - m_new_vars.push_back(r1.m_vars[i]); - m_new_vars.back().m_coeff += c*r2.m_vars[j].m_coeff; - ++i; - ++j; - if (m_new_vars.back().m_coeff.is_zero()) { - m_new_vars.pop_back(); - } - } - else if (v1 < v2) { - m_new_vars.push_back(r1.m_vars[i]); - ++i; - } - else { - m_new_vars.push_back(r2.m_vars[j]); - m_new_vars.back().m_coeff *= c; - if (row_id1 != m_objective_id) { - m_var2row_ids[r2.m_vars[j].m_id].push_back(row_id1); - } - ++j; - } - } - r1.m_coeff += c*r2.m_coeff; - r1.m_vars.swap(m_new_vars); - r1.m_value += c*r2.m_value; - - if (!same_sign && r2.m_type == t_lt) { - r1.m_type = t_lt; - } - else if (same_sign && r1.m_type == t_lt && r2.m_type == t_lt) { - r1.m_type = t_le; - } - SASSERT(invariant(row_id1, r1)); - } - - void model_based_opt::display(std::ostream& out) const { - for (auto const& r : m_rows) { - display(out, r); - } - for (unsigned i = 0; i < m_var2row_ids.size(); ++i) { - unsigned_vector const& rows = m_var2row_ids[i]; - out << i << ": "; - for (auto const& r : rows) { - out << r << " "; - } - out << "\n"; - } - } - - void model_based_opt::display(std::ostream& out, vector const& vars, rational const& coeff) { - unsigned i = 0; - for (var const& v : vars) { - if (i > 0 && v.m_coeff.is_pos()) { - out << "+ "; - } - ++i; - if (v.m_coeff.is_one()) { - out << "v" << v.m_id << " "; - } - else { - out << v.m_coeff << "*v" << v.m_id << " "; - } - } - if (coeff.is_pos()) { - out << " + " << coeff << " "; - } - else if (coeff.is_neg()) { - out << coeff << " "; - } - } - - std::ostream& model_based_opt::display(std::ostream& out, row const& r) { - out << (r.m_alive?"a":"d") << " "; - display(out, r.m_vars, r.m_coeff); - if (r.m_type == opt::t_mod) { - out << r.m_type << " " << r.m_mod << " = 0; value: " << r.m_value << "\n"; - } - else { - out << r.m_type << " 0; value: " << r.m_value << "\n"; - } - return out; - } - - std::ostream& model_based_opt::display(std::ostream& out, def const& r) { - display(out, r.m_vars, r.m_coeff); - if (!r.m_div.is_one()) { - out << " / " << r.m_div; - } - return out; - } - - unsigned model_based_opt::add_var(rational const& value, bool is_int) { - unsigned v = m_var2value.size(); - m_var2value.push_back(value); - m_var2is_int.push_back(is_int); - SASSERT(value.is_int() || !is_int); - m_var2row_ids.push_back(unsigned_vector()); - return v; - } - - rational model_based_opt::get_value(unsigned var) { - return m_var2value[var]; - } - - void model_based_opt::set_row(unsigned row_id, vector const& coeffs, rational const& c, rational const& m, ineq_type rel) { - row& r = m_rows[row_id]; - rational val(c); - SASSERT(r.m_vars.empty()); - r.m_vars.append(coeffs.size(), coeffs.data()); - bool is_int_row = !coeffs.empty(); - std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); - for (auto const& c : coeffs) { - val += m_var2value[c.m_id] * c.m_coeff; - SASSERT(!is_int(c.m_id) || c.m_coeff.is_int()); - is_int_row &= is_int(c.m_id); - } - r.m_alive = true; - r.m_coeff = c; - r.m_value = val; - r.m_type = rel; - r.m_mod = m; - if (is_int_row && rel == t_lt) { - r.m_type = t_le; - r.m_coeff += rational::one(); - r.m_value += rational::one(); - } - } - - unsigned model_based_opt::new_row() { - unsigned row_id = 0; - if (m_retired_rows.empty()) { - row_id = m_rows.size(); - m_rows.push_back(row()); - } - else { - row_id = m_retired_rows.back(); - m_retired_rows.pop_back(); - m_rows[row_id].reset(); - m_rows[row_id].m_alive = true; - } - return row_id; - } - - unsigned model_based_opt::copy_row(unsigned src) { - unsigned dst = new_row(); - row const& r = m_rows[src]; - set_row(dst, r.m_vars, r.m_coeff, r.m_mod, r.m_type); - for (auto const& v : r.m_vars) { - m_var2row_ids[v.m_id].push_back(dst); - } - SASSERT(invariant(dst, m_rows[dst])); - return dst; - } - - void model_based_opt::add_constraint(vector const& coeffs, rational const& c, ineq_type rel) { - add_constraint(coeffs, c, rational::zero(), rel); - } - - void model_based_opt::add_divides(vector const& coeffs, rational const& c, rational const& m) { - add_constraint(coeffs, c, m, t_mod); - } - - void model_based_opt::add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type rel) { - unsigned row_id = new_row(); - set_row(row_id, coeffs, c, m, rel); - for (var const& coeff : coeffs) { - m_var2row_ids[coeff.m_id].push_back(row_id); - } - SASSERT(invariant(row_id, m_rows[row_id])); - } - - void model_based_opt::set_objective(vector const& coeffs, rational const& c) { - set_row(m_objective_id, coeffs, c, rational::zero(), t_le); - } - - void model_based_opt::get_live_rows(vector& rows) { - for (row & r : m_rows) { - if (r.m_alive) { - rows.push_back(r.normalize()); - } - } - } - - // - // pick glb and lub representative. - // The representative is picked such that it - // represents the fewest inequalities. - // The constraints that enforce a glb or lub are not forced. - // The constraints that separate the glb from ub or the lub from lb - // are not forced. - // In other words, suppose there are - // . N inequalities of the form t <= x - // . M inequalities of the form s >= x - // . t0 is glb among N under valuation. - // . s0 is lub among M under valuation. - // If N < M - // create the inequalities: - // t <= t0 for each t other than t0 (N-1 inequalities). - // t0 <= s for each s (M inequalities). - // If N >= M the construction is symmetric. - // - model_based_opt::def model_based_opt::project(unsigned x, bool compute_def) { - unsigned_vector& lub_rows = m_lub; - unsigned_vector& glb_rows = m_glb; - unsigned_vector& mod_rows = m_mod; - unsigned lub_index = UINT_MAX, glb_index = UINT_MAX; - bool lub_strict = false, glb_strict = false; - rational lub_val, glb_val; - rational const& x_val = m_var2value[x]; - unsigned_vector const& row_ids = m_var2row_ids[x]; - uint_set visited; - lub_rows.reset(); - glb_rows.reset(); - mod_rows.reset(); - bool lub_is_unit = false, glb_is_unit = false; - unsigned eq_row = UINT_MAX; - // select the lub and glb. - for (unsigned row_id : row_ids) { - if (visited.contains(row_id)) { - continue; - } - visited.insert(row_id); - row& r = m_rows[row_id]; - if (!r.m_alive) { - continue; - } - rational a = get_coefficient(row_id, x); - if (a.is_zero()) { - continue; - } - if (r.m_type == t_eq) { - eq_row = row_id; - continue; - } - if (r.m_type == t_mod) { - mod_rows.push_back(row_id); - } - else if (a.is_pos()) { - rational lub_value = x_val - (r.m_value/a); - if (lub_rows.empty() || - lub_value < lub_val || - (lub_value == lub_val && r.m_type == t_lt && !lub_strict)) { - lub_val = lub_value; - lub_index = row_id; - lub_strict = r.m_type == t_lt; - } - lub_rows.push_back(row_id); - lub_is_unit &= a.is_one(); - } - else { - SASSERT(a.is_neg()); - rational glb_value = x_val - (r.m_value/a); - if (glb_rows.empty() || - glb_value > glb_val || - (glb_value == glb_val && r.m_type == t_lt && !glb_strict)) { - glb_val = glb_value; - glb_index = row_id; - glb_strict = r.m_type == t_lt; - } - glb_rows.push_back(row_id); - glb_is_unit &= a.is_minus_one(); - } - } - - if (!mod_rows.empty()) { - return solve_mod(x, mod_rows, compute_def); - } - - if (eq_row != UINT_MAX) { - return solve_for(eq_row, x, compute_def); - } - - def result; - unsigned lub_size = lub_rows.size(); - unsigned glb_size = glb_rows.size(); - unsigned row_index = (lub_size <= glb_size) ? lub_index : glb_index; - - // There are only upper or only lower bounds. - if (row_index == UINT_MAX) { - if (compute_def) { - if (lub_index != UINT_MAX) { - result = solve_for(lub_index, x, true); - } - else if (glb_index != UINT_MAX) { - result = solve_for(glb_index, x, true); - } - else { - result = def() + m_var2value[x]; - } - SASSERT(eval(result) == eval(x)); - } - else { - for (unsigned row_id : lub_rows) retire_row(row_id); - for (unsigned row_id : glb_rows) retire_row(row_id); - } - return result; - } - - SASSERT(lub_index != UINT_MAX); - SASSERT(glb_index != UINT_MAX); - if (compute_def) { - if (lub_size <= glb_size) { - result = def(m_rows[lub_index], x); - } - else { - result = def(m_rows[glb_index], x); - } - } - - // The number of matching lower and upper bounds is small. - if ((lub_size <= 2 || glb_size <= 2) && - (lub_size <= 3 && glb_size <= 3) && - (!is_int(x) || lub_is_unit || glb_is_unit)) { - for (unsigned i = 0; i < lub_size; ++i) { - unsigned row_id1 = lub_rows[i]; - bool last = i + 1 == lub_size; - rational coeff = get_coefficient(row_id1, x); - for (unsigned row_id2 : glb_rows) { - if (last) { - resolve(row_id1, coeff, row_id2, x); - } - else { - unsigned row_id3 = copy_row(row_id2); - resolve(row_id1, coeff, row_id3, x); - } - } - } - for (unsigned row_id : lub_rows) retire_row(row_id); - - return result; - } - - // General case. - rational coeff = get_coefficient(row_index, x); - for (unsigned row_id : lub_rows) { - if (row_id != row_index) { - resolve(row_index, coeff, row_id, x); - } - } - for (unsigned row_id : glb_rows) { - if (row_id != row_index) { - resolve(row_index, coeff, row_id, x); - } - } - retire_row(row_index); - return result; - } - - // - // compute D and u. - // - // D = lcm(d1, d2) - // u = eval(x) mod D - // - // d1 | (a1x + t1) & d2 | (a2x + t2) - // = - // d1 | (a1(D*x' + u) + t1) & d2 | (a2(D*x' + u) + t2) - // = - // d1 | (a1*u + t1) & d2 | (a2*u + t2) - // - // x := D*x' + u - // - - model_based_opt::def model_based_opt::solve_mod(unsigned x, unsigned_vector const& mod_rows, bool compute_def) { - SASSERT(!mod_rows.empty()); - rational D(1); - for (unsigned idx : mod_rows) { - D = lcm(D, m_rows[idx].m_mod); - } - if (D.is_zero()) { - throw default_exception("modulo 0 is not defined"); - } - if (D.is_neg()) D = abs(D); - TRACE("opt1", display(tout << "lcm: " << D << " x: v" << x << " tableau\n");); - rational val_x = m_var2value[x]; - rational u = mod(val_x, D); - SASSERT(u.is_nonneg() && u < D); - for (unsigned idx : mod_rows) { - replace_var(idx, x, u); - SASSERT(invariant(idx, m_rows[idx])); - normalize(idx); - } - TRACE("opt1", display(tout << "tableau after replace x under mod\n");); - // - // update inequalities such that u is added to t and - // D is multiplied to coefficient of x. - // the interpretation of the new version of x is (x-u)/D - // - // a*x + t <= 0 - // a*(D*x' + u) + t <= 0 - // a*D*x' + a*u + t <= 0 - // - rational new_val = (val_x - u) / D; - SASSERT(new_val.is_int()); - unsigned y = add_var(new_val, true); - unsigned_vector const& row_ids = m_var2row_ids[x]; - uint_set visited; - for (unsigned row_id : row_ids) { - if (!visited.contains(row_id)) { - // x |-> D*y + u - replace_var(row_id, x, D, y, u); - visited.insert(row_id); - normalize(row_id); - } - } - TRACE("opt1", display(tout << "tableau after replace x by y := v" << y << "\n");); - def result = project(y, compute_def); - if (compute_def) { - result = (result * D) + u; - m_var2value[x] = eval(result); - } - TRACE("opt1", display(tout << "tableau after project y" << y << "\n");); - - return result; - } - - // update row with: x |-> C - void model_based_opt::replace_var(unsigned row_id, unsigned x, rational const& C) { - row& r = m_rows[row_id]; - SASSERT(!get_coefficient(row_id, x).is_zero()); - unsigned sz = r.m_vars.size(); - unsigned i = 0, j = 0; - rational coeff(0); - for (; i < sz; ++i) { - if (r.m_vars[i].m_id == x) { - coeff = r.m_vars[i].m_coeff; - } - else { - if (i != j) { - r.m_vars[j] = r.m_vars[i]; - } - ++j; - } - } - if (j != sz) { - r.m_vars.shrink(j); - } - r.m_coeff += coeff*C; - r.m_value += coeff*(C - m_var2value[x]); - } - - // update row with: x |-> A*y + B - void model_based_opt::replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B) { - row& r = m_rows[row_id]; - rational coeff = get_coefficient(row_id, x); - if (coeff.is_zero()) return; - if (!r.m_alive) return; - replace_var(row_id, x, B); - r.m_vars.push_back(var(y, coeff*A)); - r.m_value += coeff*A*m_var2value[y]; - if (!r.m_vars.empty() && r.m_vars.back().m_id > y) { - std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); - } - m_var2row_ids[y].push_back(row_id); - SASSERT(invariant(row_id, r)); - } - - // 3x + t = 0 & 7 | (c*x + s) & ax <= u - // 3 | -t & 21 | (-ct + 3s) & a-t <= 3u - - model_based_opt::def model_based_opt::solve_for(unsigned row_id1, unsigned x, bool compute_def) { - TRACE("opt", tout << "v" << x << " := " << eval(x) << "\n" << m_rows[row_id1] << "\n";); - rational a = get_coefficient(row_id1, x), b; - row& r1 = m_rows[row_id1]; - ineq_type ty = r1.m_type; - SASSERT(!a.is_zero()); - SASSERT(r1.m_alive); - if (a.is_neg()) { - a.neg(); - r1.neg(); - } - SASSERT(a.is_pos()); - if (ty == t_lt) { - SASSERT(compute_def); - r1.m_coeff -= r1.m_value; - r1.m_type = t_le; - r1.m_value = 0; - } - - if (m_var2is_int[x] && !a.is_one()) { - r1.m_coeff -= r1.m_value; - r1.m_value = 0; - vector coeffs; - mk_coeffs_without(coeffs, r1.m_vars, x); - rational c = mod(-eval(coeffs), a); - add_divides(coeffs, c, a); - } - unsigned_vector const& row_ids = m_var2row_ids[x]; - uint_set visited; - visited.insert(row_id1); - for (unsigned row_id2 : row_ids) { - if (!visited.contains(row_id2)) { - visited.insert(row_id2); - b = get_coefficient(row_id2, x); - if (b.is_zero()) - continue; - row& dst = m_rows[row_id2]; - switch (dst.m_type) { - case t_eq: - case t_lt: - case t_le: - solve(row_id1, a, row_id2, x); - break; - case t_mod: - // mod reduction already done. - UNREACHABLE(); - break; - } - } - } - def result; - if (compute_def) { - result = def(m_rows[row_id1], x); - m_var2value[x] = eval(result); - TRACE("opt1", tout << "updated eval " << x << " := " << eval(x) << "\n";); - } - retire_row(row_id1); - return result; - } - - vector model_based_opt::project(unsigned num_vars, unsigned const* vars, bool compute_def) { - vector result; - for (unsigned i = 0; i < num_vars; ++i) { - result.push_back(project(vars[i], compute_def)); - TRACE("opt", display(tout << "After projecting: v" << vars[i] << "\n");); - } - return result; - } - -} - +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + model_based_opt.cpp + +Abstract: + + Model-based optimization and projection for linear real, integer arithmetic. + +Author: + + Nikolaj Bjorner (nbjorner) 2016-27-4 + +Revision History: + + +--*/ + +#include "math/simplex/model_based_opt.h" +#include "util/uint_set.h" +#include "util/z3_exception.h" + +std::ostream& operator<<(std::ostream& out, opt::ineq_type ie) { + switch (ie) { + case opt::t_eq: return out << " = "; + case opt::t_lt: return out << " < "; + case opt::t_le: return out << " <= "; + case opt::t_divides: return out << " divides "; + case opt::t_mod: return out << " mod "; + case opt::t_div: return out << " div "; + } + return out; +} + + +namespace opt { + + /** + * Convert a row ax + coeffs + coeff = value into a definition for x + * x = (value - coeffs - coeff)/a + * as backdrop we have existing assignments to x and other variables that + * satisfy the equality with value, and such that value satisfies + * the row constraint ( = , <= , < , mod) + */ + model_based_opt::def::def(row const& r, unsigned x) { + for (var const & v : r.m_vars) { + if (v.m_id != x) { + m_vars.push_back(v); + } + else { + m_div = -v.m_coeff; + } + } + m_coeff = r.m_coeff; + switch (r.m_type) { + case opt::t_lt: + m_coeff += m_div; + break; + case opt::t_le: + // for: ax >= t, then x := (t + a - 1) div a + if (m_div.is_pos()) { + m_coeff += m_div; + m_coeff -= rational::one(); + } + break; + default: + break; + } + normalize(); + SASSERT(m_div.is_pos()); + } + + model_based_opt::def model_based_opt::def::operator+(def const& other) const { + def result; + vector const& vs1 = m_vars; + vector const& vs2 = other.m_vars; + vector & vs = result.m_vars; + rational c1(1), c2(1); + if (m_div != other.m_div) { + c1 = other.m_div; + c2 = m_div; + } + unsigned i = 0, j = 0; + while (i < vs1.size() || j < vs2.size()) { + unsigned v1 = UINT_MAX, v2 = UINT_MAX; + if (i < vs1.size()) v1 = vs1[i].m_id; + if (j < vs2.size()) v2 = vs2[j].m_id; + if (v1 == v2) { + vs.push_back(vs1[i]); + vs.back().m_coeff *= c1; + vs.back().m_coeff += c2 * vs2[j].m_coeff; + ++i; ++j; + if (vs.back().m_coeff.is_zero()) { + vs.pop_back(); + } + } + else if (v1 < v2) { + vs.push_back(vs1[i]); + vs.back().m_coeff *= c1; + } + else { + vs.push_back(vs2[j]); + vs.back().m_coeff *= c2; + } + } + result.m_div = c1*m_div; + result.m_coeff = (m_coeff*c1) + (other.m_coeff*c2); + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator/(rational const& r) const { + def result(*this); + result.m_div *= r; + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator*(rational const& n) const { + def result(*this); + for (var& v : result.m_vars) { + v.m_coeff *= n; + } + result.m_coeff *= n; + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator+(rational const& n) const { + def result(*this); + result.m_coeff += n * result.m_div; + result.normalize(); + return result; + } + + void model_based_opt::def::normalize() { + if (!m_div.is_int()) { + rational den = denominator(m_div); + SASSERT(den > 1); + for (var& v : m_vars) + v.m_coeff *= den; + m_coeff *= den; + m_div *= den; + + } + if (m_div.is_neg()) { + for (var& v : m_vars) + v.m_coeff.neg(); + m_coeff.neg(); + m_div.neg(); + } + if (m_div.is_one()) + return; + rational g(m_div); + if (!m_coeff.is_int()) + return; + g = gcd(g, m_coeff); + for (var const& v : m_vars) { + if (!v.m_coeff.is_int()) + return; + g = gcd(g, abs(v.m_coeff)); + if (g.is_one()) + break; + } + if (!g.is_one()) { + for (var& v : m_vars) + v.m_coeff /= g; + m_coeff /= g; + m_div /= g; + } + } + + model_based_opt::model_based_opt() { + m_rows.push_back(row()); + } + + bool model_based_opt::invariant() { + for (unsigned i = 0; i < m_rows.size(); ++i) { + if (!invariant(i, m_rows[i])) { + return false; + } + } + return true; + } + +#define PASSERT(_e_) { CTRACE("qe", !(_e_), display(tout, r); display(tout);); SASSERT(_e_); } + + bool model_based_opt::invariant(unsigned index, row const& r) { + vector const& vars = r.m_vars; + for (unsigned i = 0; i < vars.size(); ++i) { + // variables in each row are sorted and have non-zero coefficients + PASSERT(i + 1 == vars.size() || vars[i].m_id < vars[i+1].m_id); + PASSERT(!vars[i].m_coeff.is_zero()); + PASSERT(index == 0 || m_var2row_ids[vars[i].m_id].contains(index)); + } + + PASSERT(r.m_value == eval(r)); + PASSERT(r.m_type != t_eq || r.m_value.is_zero()); + // values satisfy constraints + PASSERT(index == 0 || r.m_type != t_lt || r.m_value.is_neg()); + PASSERT(index == 0 || r.m_type != t_le || !r.m_value.is_pos()); + PASSERT(index == 0 || r.m_type != t_divides || (mod(r.m_value, r.m_mod).is_zero())); + PASSERT(index == 0 || r.m_type != t_mod || r.m_id < m_var2value.size()); + PASSERT(index == 0 || r.m_type != t_div || r.m_id < m_var2value.size()); + return true; + } + + // a1*x + obj + // a2*x + t2 <= 0 + // a3*x + t3 <= 0 + // a4*x + t4 <= 0 + // a1 > 0, a2 > 0, a3 > 0, a4 < 0 + // x <= -t2/a2 + // x <= -t2/a3 + // determine lub among these. + // then resolve lub with others + // e.g., -t2/a2 <= -t3/a3, then + // replace inequality a3*x + t3 <= 0 by -t2/a2 + t3/a3 <= 0 + // mark a4 as invalid. + // + + // a1 < 0, a2 < 0, a3 < 0, a4 > 0 + // x >= t2/a2 + // x >= t3/a3 + // determine glb among these + // the resolve glb with others. + // e.g. t2/a2 >= t3/a3 + // then replace a3*x + t3 by t3/a3 - t2/a2 <= 0 + // + inf_eps model_based_opt::maximize() { + SASSERT(invariant()); + unsigned_vector bound_trail, bound_vars; + TRACE("opt", display(tout << "tableau\n");); + while (!objective().m_vars.empty()) { + var v = objective().m_vars.back(); + unsigned x = v.m_id; + rational const& coeff = v.m_coeff; + unsigned bound_row_index; + rational bound_coeff; + if (find_bound(x, bound_row_index, bound_coeff, coeff.is_pos())) { + SASSERT(!bound_coeff.is_zero()); + TRACE("opt", display(tout << "update: " << v << " ", objective()); + for (unsigned above : m_above) { + display(tout << "resolve: ", m_rows[above]); + }); + for (unsigned above : m_above) { + resolve(bound_row_index, bound_coeff, above, x); + } + for (unsigned below : m_below) { + resolve(bound_row_index, bound_coeff, below, x); + } + // coeff*x + objective <= ub + // a2*x + t2 <= 0 + // => coeff*x <= -t2*coeff/a2 + // objective + t2*coeff/a2 <= ub + + mul_add(false, m_objective_id, - coeff/bound_coeff, bound_row_index); + retire_row(bound_row_index); + bound_trail.push_back(bound_row_index); + bound_vars.push_back(x); + } + else { + TRACE("opt", display(tout << "unbound: " << v << " ", objective());); + update_values(bound_vars, bound_trail); + return inf_eps::infinity(); + } + } + + // + // update the evaluation of variables to satisfy the bound. + // + + update_values(bound_vars, bound_trail); + + rational value = objective().m_value; + if (objective().m_type == t_lt) { + return inf_eps(inf_rational(value, rational(-1))); + } + else { + return inf_eps(inf_rational(value)); + } + } + + + void model_based_opt::update_value(unsigned x, rational const& val) { + rational old_val = m_var2value[x]; + m_var2value[x] = val; + SASSERT(val.is_int() || !is_int(x)); + unsigned_vector const& row_ids = m_var2row_ids[x]; + for (unsigned row_id : row_ids) { + rational coeff = get_coefficient(row_id, x); + if (coeff.is_zero()) { + continue; + } + row & r = m_rows[row_id]; + rational delta = coeff * (val - old_val); + r.m_value += delta; + SASSERT(invariant(row_id, r)); + } + } + + + void model_based_opt::update_values(unsigned_vector const& bound_vars, unsigned_vector const& bound_trail) { + for (unsigned i = bound_trail.size(); i-- > 0; ) { + unsigned x = bound_vars[i]; + row& r = m_rows[bound_trail[i]]; + rational val = r.m_coeff; + rational old_x_val = m_var2value[x]; + rational new_x_val; + rational x_coeff, eps(0); + vector const& vars = r.m_vars; + for (var const& v : vars) { + if (x == v.m_id) { + x_coeff = v.m_coeff; + } + else { + val += m_var2value[v.m_id]*v.m_coeff; + } + } + SASSERT(!x_coeff.is_zero()); + new_x_val = -val/x_coeff; + + if (r.m_type == t_lt) { + eps = abs(old_x_val - new_x_val)/rational(2); + eps = std::min(rational::one(), eps); + SASSERT(!eps.is_zero()); + + // + // ax + t < 0 + // <=> x < -t/a + // <=> x := -t/a - epsilon + // + if (x_coeff.is_pos()) { + new_x_val -= eps; + } + // + // -ax + t < 0 + // <=> -ax < -t + // <=> -x < -t/a + // <=> x > t/a + // <=> x := t/a + epsilon + // + else { + new_x_val += eps; + } + } + TRACE("opt", display(tout << "v" << x + << " coeff_x: " << x_coeff + << " old_x_val: " << old_x_val + << " new_x_val: " << new_x_val + << " eps: " << eps << " ", r); ); + m_var2value[x] = new_x_val; + + r.m_value = eval(r); + SASSERT(invariant(bound_trail[i], r)); + } + + // update and check bounds for all other affected rows. + for (unsigned i = bound_trail.size(); i-- > 0; ) { + unsigned x = bound_vars[i]; + unsigned_vector const& row_ids = m_var2row_ids[x]; + for (unsigned row_id : row_ids) { + row & r = m_rows[row_id]; + r.m_value = eval(r); + SASSERT(invariant(row_id, r)); + } + } + SASSERT(invariant()); + } + + bool model_based_opt::find_bound(unsigned x, unsigned& bound_row_index, rational& bound_coeff, bool is_pos) { + bound_row_index = UINT_MAX; + rational lub_val; + rational const& x_val = m_var2value[x]; + unsigned_vector const& row_ids = m_var2row_ids[x]; + uint_set visited; + m_above.reset(); + m_below.reset(); + for (unsigned row_id : row_ids) { + SASSERT(row_id != m_objective_id); + if (visited.contains(row_id)) + continue; + visited.insert(row_id); + row& r = m_rows[row_id]; + if (!r.m_alive) + continue; + rational a = get_coefficient(row_id, x); + if (a.is_zero()) { + // skip + } + else if (a.is_pos() == is_pos || r.m_type == t_eq) { + rational value = x_val - (r.m_value/a); + if (bound_row_index == UINT_MAX) { + lub_val = value; + bound_row_index = row_id; + bound_coeff = a; + } + else if ((value == lub_val && r.m_type == opt::t_lt) || + (is_pos && value < lub_val) || + + (!is_pos && value > lub_val)) { + m_above.push_back(bound_row_index); + lub_val = value; + bound_row_index = row_id; + bound_coeff = a; + } + else + m_above.push_back(row_id); + } + else + m_below.push_back(row_id); + } + return bound_row_index != UINT_MAX; + } + + void model_based_opt::retire_row(unsigned row_id) { + SASSERT(!m_retired_rows.contains(row_id)); + m_rows[row_id].m_alive = false; + m_retired_rows.push_back(row_id); + } + + rational model_based_opt::eval(unsigned x) const { + return m_var2value[x]; + } + + rational model_based_opt::eval(def const& d) const { + vector const& vars = d.m_vars; + rational val = d.m_coeff; + for (var const& v : vars) { + val += v.m_coeff * eval(v.m_id); + } + val /= d.m_div; + return val; + } + + rational model_based_opt::eval(row const& r) const { + vector const& vars = r.m_vars; + rational val = r.m_coeff; + for (var const& v : vars) { + val += v.m_coeff * eval(v.m_id); + } + return val; + } + + rational model_based_opt::eval(vector const& coeffs) const { + rational val(0); + for (var const& v : coeffs) + val += v.m_coeff * eval(v.m_id); + return val; + } + + rational model_based_opt::get_coefficient(unsigned row_id, unsigned var_id) const { + return m_rows[row_id].get_coefficient(var_id); + } + + rational model_based_opt::row::get_coefficient(unsigned var_id) const { + if (m_vars.empty()) + return rational::zero(); + unsigned lo = 0, hi = m_vars.size(); + while (lo < hi) { + unsigned mid = lo + (hi - lo)/2; + SASSERT(mid < hi); + unsigned id = m_vars[mid].m_id; + if (id == var_id) { + lo = mid; + break; + } + if (id < var_id) + lo = mid + 1; + else + hi = mid; + } + if (lo == m_vars.size()) + return rational::zero(); + unsigned id = m_vars[lo].m_id; + if (id == var_id) + return m_vars[lo].m_coeff; + else + return rational::zero(); + } + + model_based_opt::row& model_based_opt::row::normalize() { +#if 0 + if (m_type == t_divides || m_type == t_mod || m_type == t_div) + return *this; + rational D(denominator(abs(m_coeff))); + if (D == 0) + D = 1; + for (auto const& [id, coeff] : m_vars) + if (coeff != 0) + D = lcm(D, denominator(abs(coeff))); + if (D == 1) + return *this; + SASSERT(D > 0); + for (auto & [id, coeff] : m_vars) + coeff *= D; + m_coeff *= D; +#endif + return *this; + } + + // + // Let + // row1: t1 + a1*x <= 0 + // row2: t2 + a2*x <= 0 + // + // assume a1, a2 have the same signs: + // (t2 + a2*x) <= (t1 + a1*x)*a2/a1 + // <=> t2*a1/a2 - t1 <= 0 + // <=> t2 - t1*a2/a1 <= 0 + // + // assume a1 > 0, -a2 < 0: + // t1 + a1*x <= 0, t2 - a2*x <= 0 + // t2/a2 <= -t1/a1 + // t2 + t1*a2/a1 <= 0 + // assume -a1 < 0, a2 > 0: + // t1 - a1*x <= 0, t2 + a2*x <= 0 + // t1/a1 <= -t2/a2 + // t2 + t1*a2/a1 <= 0 + // + // the resolvent is the same in all cases (simpler proof should exist) + // + // assume a1 < 0, -a1 = a2: + // t1 <= a2*div(t2, a2) + // + + void model_based_opt::resolve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x) { + + SASSERT(a1 == get_coefficient(row_src, x)); + SASSERT(!a1.is_zero()); + SASSERT(row_src != row_dst); + + if (m_rows[row_dst].m_alive) { + rational a2 = get_coefficient(row_dst, x); + if (is_int(x)) { + TRACE("opt", + tout << x << ": " << a1 << " " << a2 << ": "; + display(tout, m_rows[row_dst]); + display(tout, m_rows[row_src]);); + if (a1.is_pos() != a2.is_pos() || m_rows[row_src].m_type == opt::t_eq) { + mul_add(x, a1, row_src, a2, row_dst); + } + else { + mul(row_dst, abs(a1)); + mul_add(false, row_dst, -abs(a2), row_src); + } + TRACE("opt", display(tout << "result ", m_rows[row_dst]);); + normalize(row_dst); + } + else { + mul_add(row_dst != m_objective_id && a1.is_pos() == a2.is_pos(), row_dst, -a2/a1, row_src); + } + } + } + + /** + * a1 > 0 + * a1*x + r1 = value + * a2*x + r2 <= 0 + * ------------------ + * a1*r2 - a2*r1 <= value + */ + void model_based_opt::solve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x) { + SASSERT(a1 == get_coefficient(row_src, x)); + SASSERT(a1.is_pos()); + SASSERT(row_src != row_dst); + if (!m_rows[row_dst].m_alive) return; + rational a2 = get_coefficient(row_dst, x); + mul(row_dst, a1); + mul_add(false, row_dst, -a2, row_src); + normalize(row_dst); + SASSERT(get_coefficient(row_dst, x).is_zero()); + } + + // resolution for integer rows. + void model_based_opt::mul_add( + unsigned x, rational src_c, unsigned row_src, rational dst_c, unsigned row_dst) { + row& dst = m_rows[row_dst]; + row const& src = m_rows[row_src]; + SASSERT(is_int(x)); + SASSERT(t_le == dst.m_type && t_le == src.m_type); + SASSERT(src_c.is_int()); + SASSERT(dst_c.is_int()); + SASSERT(m_var2value[x].is_int()); + + rational abs_src_c = abs(src_c); + rational abs_dst_c = abs(dst_c); + rational x_val = m_var2value[x]; + rational slack = (abs_src_c - rational::one()) * (abs_dst_c - rational::one()); + rational dst_val = dst.m_value - x_val*dst_c; + rational src_val = src.m_value - x_val*src_c; + rational distance = abs_src_c * dst_val + abs_dst_c * src_val + slack; + bool use_case1 = distance.is_nonpos() || abs_src_c.is_one() || abs_dst_c.is_one(); + bool use_case2 = false && abs_src_c == abs_dst_c && src_c.is_pos() != dst_c.is_pos() && !abs_src_c.is_one() && t_le == dst.m_type && t_le == src.m_type; + bool use_case3 = false && src_c.is_pos() != dst_c.is_pos() && t_le == dst.m_type && t_le == src.m_type; + + + if (use_case1) { + TRACE("opt", tout << "slack: " << slack << " " << src_c << " " << dst_val << " " << dst_c << " " << src_val << "\n";); + // dst <- abs_src_c*dst + abs_dst_c*src + slack + mul(row_dst, abs_src_c); + add(row_dst, slack); + mul_add(false, row_dst, abs_dst_c, row_src); + return; + } + + if (use_case2 || use_case3) { + // case2: + // x*src_c + s <= 0 + // -x*src_c + t <= 0 + // + // -src_c*div(-s, src_c) + t <= 0 + // + // Example: + // t <= 100*x <= s + // Then t <= 100*div(s, 100) + // + // case3: + // x*src_c + s <= 0 + // -x*dst_c + t <= 0 + // t <= x*dst_c, x*src_c <= -s -> + // t <= dst_c*div(-s, src_c) -> + // -dst_c*div(-s,src_c) + t <= 0 + // + + bool swapped = false; + if (src_c < 0) { + std::swap(row_src, row_dst); + std::swap(src_c, dst_c); + std::swap(abs_src_c, abs_dst_c); + swapped = true; + } + vector src_coeffs, dst_coeffs; + rational src_coeff = m_rows[row_src].m_coeff; + rational dst_coeff = m_rows[row_dst].m_coeff; + for (auto const& v : m_rows[row_src].m_vars) + if (v.m_id != x) + src_coeffs.push_back(var(v.m_id, -v.m_coeff)); + for (auto const& v : m_rows[row_dst].m_vars) + if (v.m_id != x) + dst_coeffs.push_back(v); + unsigned v = UINT_MAX; + if (src_coeffs.empty()) + dst_coeff -= abs_dst_c*div(-src_coeff, abs_src_c); + else + v = add_div(src_coeffs, -src_coeff, abs_src_c); + if (v != UINT_MAX) dst_coeffs.push_back(var(v, -abs_dst_c)); + if (swapped) + std::swap(row_src, row_dst); + retire_row(row_dst); + add_constraint(dst_coeffs, dst_coeff, t_le); + return; + } + + // + // create finite disjunction for |b|. + // exists x, z in [0 .. |b|-2] . b*x + s + z = 0 && ax + t <= 0 && bx + s <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && ax + t <= 0 && bx + s <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && bx + s <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && -z - s + s <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 && -z <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && a|b|x + |b|t <= 0 + // <=> + // exists x, z in [0 .. |b|-2] . b*x = -z - s && a*n_sign(b)(s + z) + |b|t <= 0 + // <=> + // exists z in [0 .. |b|-2] . |b| | (z + s) && a*n_sign(b)(s + z) + |b|t <= 0 + // + + TRACE("qe", tout << "finite disjunction " << distance << " " << src_c << " " << dst_c << "\n";); + vector coeffs; + if (abs_dst_c <= abs_src_c) { + rational z = mod(dst_val, abs_dst_c); + if (!z.is_zero()) z = abs_dst_c - z; + mk_coeffs_without(coeffs, dst.m_vars, x); + add_divides(coeffs, dst.m_coeff + z, abs_dst_c); + add(row_dst, z); + mul(row_dst, src_c * n_sign(dst_c)); + mul_add(false, row_dst, abs_dst_c, row_src); + } + else { + // z := b - (s + bx) mod b + // := b - s mod b + // b | s + z <=> b | s + b - s mod b <=> b | s - s mod b + rational z = mod(src_val, abs_src_c); + if (!z.is_zero()) z = abs_src_c - z; + mk_coeffs_without(coeffs, src.m_vars, x); + add_divides(coeffs, src.m_coeff + z, abs_src_c); + mul(row_dst, abs_src_c); + add(row_dst, z * dst_c * n_sign(src_c)); + mul_add(false, row_dst, dst_c * n_sign(src_c), row_src); + } + } + + void model_based_opt::mk_coeffs_without(vector& dst, vector const& src, unsigned x) { + for (var const & v : src) { + if (v.m_id != x) dst.push_back(v); + } + } + + rational model_based_opt::n_sign(rational const& b) const { + return rational(b.is_pos()?-1:1); + } + + void model_based_opt::mul(unsigned dst, rational const& c) { + if (c.is_one()) + return; + row& r = m_rows[dst]; + for (auto & v : r.m_vars) + v.m_coeff *= c; + r.m_mod *= c; + r.m_coeff *= c; + if (r.m_type != t_div && r.m_type != t_mod) + r.m_value *= c; + } + + void model_based_opt::add(unsigned dst, rational const& c) { + row& r = m_rows[dst]; + r.m_coeff += c; + r.m_value += c; + } + + void model_based_opt::sub(unsigned dst, rational const& c) { + row& r = m_rows[dst]; + r.m_coeff -= c; + r.m_value -= c; + } + + void model_based_opt::normalize(unsigned row_id) { + row& r = m_rows[row_id]; + if (!r.m_alive) + return; + if (r.m_vars.empty()) { + retire_row(row_id); + return; + } + if (r.m_type == t_divides) + return; + if (r.m_type == t_mod) + return; + if (r.m_type == t_div) + return; + rational g(abs(r.m_vars[0].m_coeff)); + bool all_int = g.is_int(); + for (unsigned i = 1; all_int && !g.is_one() && i < r.m_vars.size(); ++i) { + rational const& coeff = r.m_vars[i].m_coeff; + if (coeff.is_int()) { + g = gcd(g, abs(coeff)); + } + else { + all_int = false; + } + } + if (all_int && !r.m_coeff.is_zero()) { + if (r.m_coeff.is_int()) { + g = gcd(g, abs(r.m_coeff)); + } + else { + all_int = false; + } + } + if (all_int && !g.is_one()) { + SASSERT(!g.is_zero()); + mul(row_id, rational::one()/g); + } + } + + // + // set row1 <- row1 + c*row2 + // + void model_based_opt::mul_add(bool same_sign, unsigned row_id1, rational const& c, unsigned row_id2) { + if (c.is_zero()) + return; + + + m_new_vars.reset(); + row& r1 = m_rows[row_id1]; + row const& r2 = m_rows[row_id2]; + unsigned i = 0, j = 0; + while (i < r1.m_vars.size() || j < r2.m_vars.size()) { + if (j == r2.m_vars.size()) { + m_new_vars.append(r1.m_vars.size() - i, r1.m_vars.data() + i); + break; + } + if (i == r1.m_vars.size()) { + for (; j < r2.m_vars.size(); ++j) { + m_new_vars.push_back(r2.m_vars[j]); + m_new_vars.back().m_coeff *= c; + if (row_id1 != m_objective_id) + m_var2row_ids[r2.m_vars[j].m_id].push_back(row_id1); + } + break; + } + + unsigned v1 = r1.m_vars[i].m_id; + unsigned v2 = r2.m_vars[j].m_id; + if (v1 == v2) { + m_new_vars.push_back(r1.m_vars[i]); + m_new_vars.back().m_coeff += c*r2.m_vars[j].m_coeff; + ++i; + ++j; + if (m_new_vars.back().m_coeff.is_zero()) + m_new_vars.pop_back(); + } + else if (v1 < v2) { + m_new_vars.push_back(r1.m_vars[i]); + ++i; + } + else { + m_new_vars.push_back(r2.m_vars[j]); + m_new_vars.back().m_coeff *= c; + if (row_id1 != m_objective_id) + m_var2row_ids[r2.m_vars[j].m_id].push_back(row_id1); + ++j; + } + } + r1.m_coeff += c*r2.m_coeff; + r1.m_vars.swap(m_new_vars); + r1.m_value += c*r2.m_value; + + if (!same_sign && r2.m_type == t_lt) + r1.m_type = t_lt; + else if (same_sign && r1.m_type == t_lt && r2.m_type == t_lt) + r1.m_type = t_le; + SASSERT(invariant(row_id1, r1)); + } + + void model_based_opt::display(std::ostream& out) const { + for (auto const& r : m_rows) + display(out, r); + for (unsigned i = 0; i < m_var2row_ids.size(); ++i) { + unsigned_vector const& rows = m_var2row_ids[i]; + out << i << ": "; + for (auto const& r : rows) + out << r << " "; + out << "\n"; + } + } + + void model_based_opt::display(std::ostream& out, vector const& vars, rational const& coeff) { + unsigned i = 0; + for (var const& v : vars) { + if (i > 0 && v.m_coeff.is_pos()) + out << "+ "; + ++i; + if (v.m_coeff.is_one()) + out << "v" << v.m_id << " "; + else + out << v.m_coeff << "*v" << v.m_id << " "; + } + if (coeff.is_pos()) + out << " + " << coeff << " "; + else if (coeff.is_neg()) + out << coeff << " "; + } + + std::ostream& model_based_opt::display(std::ostream& out, row const& r) { + out << (r.m_alive?"a":"d") << " "; + display(out, r.m_vars, r.m_coeff); + switch (r.m_type) { + case opt::t_divides: + out << r.m_type << " " << r.m_mod << " = 0; value: " << r.m_value << "\n"; + break; + case opt::t_mod: + out << r.m_type << " " << r.m_mod << " = v" << r.m_id << " ; mod: " << mod(r.m_value, r.m_mod) << "\n"; + break; + case opt::t_div: + out << r.m_type << " " << r.m_mod << " = v" << r.m_id << " ; div: " << div(r.m_value, r.m_mod) << "\n"; + break; + default: + out << r.m_type << " 0; value: " << r.m_value << "\n"; + break; + } + return out; + } + + std::ostream& model_based_opt::display(std::ostream& out, def const& r) { + display(out, r.m_vars, r.m_coeff); + if (!r.m_div.is_one()) { + out << " / " << r.m_div; + } + return out; + } + + unsigned model_based_opt::add_var(rational const& value, bool is_int) { + unsigned v = m_var2value.size(); + m_var2value.push_back(value); + m_var2is_int.push_back(is_int); + SASSERT(value.is_int() || !is_int); + m_var2row_ids.push_back(unsigned_vector()); + return v; + } + + rational model_based_opt::get_value(unsigned var) { + return m_var2value[var]; + } + + void model_based_opt::set_row(unsigned row_id, vector const& coeffs, rational const& c, rational const& m, ineq_type rel) { + row& r = m_rows[row_id]; + rational val(c); + SASSERT(r.m_vars.empty()); + r.m_vars.append(coeffs.size(), coeffs.data()); + bool is_int_row = !coeffs.empty(); + std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); + for (auto const& c : coeffs) { + val += m_var2value[c.m_id] * c.m_coeff; + SASSERT(!is_int(c.m_id) || c.m_coeff.is_int()); + is_int_row &= is_int(c.m_id); + } + r.m_alive = true; + r.m_coeff = c; + r.m_value = val; + r.m_type = rel; + r.m_mod = m; + if (is_int_row && rel == t_lt) { + r.m_type = t_le; + r.m_coeff += rational::one(); + r.m_value += rational::one(); + } + } + + unsigned model_based_opt::new_row() { + unsigned row_id = 0; + if (m_retired_rows.empty()) { + row_id = m_rows.size(); + m_rows.push_back(row()); + } + else { + row_id = m_retired_rows.back(); + m_retired_rows.pop_back(); + SASSERT(!m_rows[row_id].m_alive); + m_rows[row_id].reset(); + m_rows[row_id].m_alive = true; + } + return row_id; + } + + unsigned model_based_opt::copy_row(unsigned src, unsigned excl) { + unsigned dst = new_row(); + row const& r = m_rows[src]; + set_row(dst, r.m_vars, r.m_coeff, r.m_mod, r.m_type); + for (auto const& v : r.m_vars) { + if (v.m_id != excl) + m_var2row_ids[v.m_id].push_back(dst); + } + SASSERT(invariant(dst, m_rows[dst])); + return dst; + } + + void model_based_opt::add_lower_bound(unsigned x, rational const& lo) { + vector coeffs; + coeffs.push_back(var(x, rational::minus_one())); + add_constraint(coeffs, lo, t_le); + } + + void model_based_opt::add_upper_bound(unsigned x, rational const& hi) { + vector coeffs; + coeffs.push_back(var(x, rational::one())); + add_constraint(coeffs, -hi, t_le); + } + + void model_based_opt::add_constraint(vector const& coeffs, rational const& c, ineq_type rel) { + add_constraint(coeffs, c, rational::zero(), rel, 0); + } + + void model_based_opt::add_divides(vector const& coeffs, rational const& c, rational const& m) { + rational g(c); + for (auto const& [v, coeff] : coeffs) + g = gcd(coeff, g); + if ((g/m).is_int()) + return; + add_constraint(coeffs, c, m, t_divides, 0); + } + + unsigned model_based_opt::add_mod(vector const& coeffs, rational const& c, rational const& m) { + rational value = c; + for (auto const& var : coeffs) + value += var.m_coeff * m_var2value[var.m_id]; + unsigned v = add_var(mod(value, m), true); + add_constraint(coeffs, c, m, t_mod, v); + return v; + } + + unsigned model_based_opt::add_div(vector const& coeffs, rational const& c, rational const& m) { + rational value = c; + for (auto const& var : coeffs) + value += var.m_coeff * m_var2value[var.m_id]; + unsigned v = add_var(div(value, m), true); + add_constraint(coeffs, c, m, t_div, v); + return v; + } + + unsigned model_based_opt::add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type rel, unsigned id) { + auto const& r = m_rows.back(); + if (r.m_vars == coeffs && r.m_coeff == c && r.m_mod == m && r.m_type == rel && r.m_id == id && r.m_alive) + return m_rows.size() - 1; + unsigned row_id = new_row(); + set_row(row_id, coeffs, c, m, rel); + m_rows[row_id].m_id = id; + for (var const& coeff : coeffs) + m_var2row_ids[coeff.m_id].push_back(row_id); + SASSERT(invariant(row_id, m_rows[row_id])); + normalize(row_id); + return row_id; + } + + void model_based_opt::set_objective(vector const& coeffs, rational const& c) { + set_row(m_objective_id, coeffs, c, rational::zero(), t_le); + } + + void model_based_opt::get_live_rows(vector& rows) { + for (row & r : m_rows) + if (r.m_alive) + rows.push_back(r.normalize()); + } + + // + // pick glb and lub representative. + // The representative is picked such that it + // represents the fewest inequalities. + // The constraints that enforce a glb or lub are not forced. + // The constraints that separate the glb from ub or the lub from lb + // are not forced. + // In other words, suppose there are + // . N inequalities of the form t <= x + // . M inequalities of the form s >= x + // . t0 is glb among N under valuation. + // . s0 is lub among M under valuation. + // If N < M + // create the inequalities: + // t <= t0 for each t other than t0 (N-1 inequalities). + // t0 <= s for each s (M inequalities). + // If N >= M the construction is symmetric. + // + model_based_opt::def model_based_opt::project(unsigned x, bool compute_def) { + unsigned_vector& lub_rows = m_lub; + unsigned_vector& glb_rows = m_glb; + unsigned_vector& divide_rows = m_divides; + unsigned_vector& mod_rows = m_mod; + unsigned_vector& div_rows = m_div; + unsigned lub_index = UINT_MAX, glb_index = UINT_MAX; + bool lub_strict = false, glb_strict = false; + rational lub_val, glb_val; + rational const& x_val = m_var2value[x]; + unsigned_vector const& row_ids = m_var2row_ids[x]; + uint_set visited; + lub_rows.reset(); + glb_rows.reset(); + divide_rows.reset(); + mod_rows.reset(); + div_rows.reset(); + bool lub_is_unit = true, glb_is_unit = true; + unsigned eq_row = UINT_MAX; + // select the lub and glb. + for (unsigned row_id : row_ids) { + if (visited.contains(row_id)) + continue; + visited.insert(row_id); + row& r = m_rows[row_id]; + if (!r.m_alive) + continue; + rational a = get_coefficient(row_id, x); + if (a.is_zero()) + continue; + if (r.m_type == t_eq) + eq_row = row_id; + else if (r.m_type == t_mod) + mod_rows.push_back(row_id); + else if (r.m_type == t_div) + div_rows.push_back(row_id); + else if (r.m_type == t_divides) + divide_rows.push_back(row_id); + else if (a.is_pos()) { + rational lub_value = x_val - (r.m_value/a); + if (lub_rows.empty() || + lub_value < lub_val || + (lub_value == lub_val && r.m_type == t_lt && !lub_strict)) { + lub_val = lub_value; + lub_index = row_id; + lub_strict = r.m_type == t_lt; + } + lub_rows.push_back(row_id); + lub_is_unit &= a.is_one(); + } + else { + SASSERT(a.is_neg()); + rational glb_value = x_val - (r.m_value/a); + if (glb_rows.empty() || + glb_value > glb_val || + (glb_value == glb_val && r.m_type == t_lt && !glb_strict)) { + glb_val = glb_value; + glb_index = row_id; + glb_strict = r.m_type == t_lt; + } + glb_rows.push_back(row_id); + glb_is_unit &= a.is_minus_one(); + } + } + + if (!divide_rows.empty()) + return solve_divides(x, divide_rows, compute_def); + + if (!div_rows.empty() || !mod_rows.empty()) + return solve_mod_div(x, mod_rows, div_rows, compute_def); + + if (eq_row != UINT_MAX) + return solve_for(eq_row, x, compute_def); + + def result; + unsigned lub_size = lub_rows.size(); + unsigned glb_size = glb_rows.size(); + unsigned row_index = (lub_size <= glb_size) ? lub_index : glb_index; + + // There are only upper or only lower bounds. + if (row_index == UINT_MAX) { + if (compute_def) { + if (lub_index != UINT_MAX) + result = solve_for(lub_index, x, true); + else if (glb_index != UINT_MAX) + result = solve_for(glb_index, x, true); + else + result = def() + m_var2value[x]; + SASSERT(eval(result) == eval(x)); + } + else { + for (unsigned row_id : lub_rows) retire_row(row_id); + for (unsigned row_id : glb_rows) retire_row(row_id); + } + return result; + } + + SASSERT(lub_index != UINT_MAX); + SASSERT(glb_index != UINT_MAX); + if (compute_def) { + if (lub_size <= glb_size) + result = def(m_rows[lub_index], x); + else + result = def(m_rows[glb_index], x); + } + + // The number of matching lower and upper bounds is small. + if ((lub_size <= 2 || glb_size <= 2) && + (lub_size <= 3 && glb_size <= 3) && + (!is_int(x) || lub_is_unit || glb_is_unit)) { + for (unsigned i = 0; i < lub_size; ++i) { + unsigned row_id1 = lub_rows[i]; + bool last = i + 1 == lub_size; + rational coeff = get_coefficient(row_id1, x); + for (unsigned row_id2 : glb_rows) { + if (last) { + resolve(row_id1, coeff, row_id2, x); + } + else { + unsigned row_id3 = copy_row(row_id2); + resolve(row_id1, coeff, row_id3, x); + } + } + } + for (unsigned row_id : lub_rows) + retire_row(row_id); + + return result; + } + + // General case. + rational coeff = get_coefficient(row_index, x); + + for (unsigned row_id : lub_rows) + if (row_id != row_index) + resolve(row_index, coeff, row_id, x); + + for (unsigned row_id : glb_rows) + if (row_id != row_index) + resolve(row_index, coeff, row_id, x); + retire_row(row_index); + return result; + } + + + // + // Given v = a*x + b mod K + // + // - remove v = a*x + b mod K + // + // case a = 1: + // - add w = b mod K + // - x |-> K*y + z, 0 <= z < K + // - if z.value + w.value < K: + // add z + w - v = 0 + // - if z.value + w.value >= K: + // add z + w - v - K = 0 + // + // case a != 1, gcd(a, K) = 1 + // - x |-> x*y + a^-1*z, 0 <= z < K + // - add w = b mod K + // if z.value + w.value < K + // add z + w - v = 0 + // if z.value + w.value >= K + // add z + w - v - K = 0 + // + // case a != 1, gcd(a,K) = g != 1 + // - x |-> x*y + a^-1*z, 0 <= z < K + // a*x + b mod K = v is now + // g*z + b mod K = v + // - add w = b mod K + // - 0 <= g*z.value + w.value < K*(g+1) + // - add g*z + w - v - k*K = 0 for suitable k from 0 .. g based on model + // + // + // + // Given v = a*x + b div K + // Replace x |-> K*y + z + // - w = b div K + // - v = ((a*K*y + a*z) + b) div K + // = a*y + (a*z + b) div K + // = a*y + b div K + (b mod K + a*z) div K + // = a*y + b div K + k + // where k := (b.value mod K + a*z.value) div K + // k is between 0 and a + // + // - k*K <= b mod K + a*z < (k+1)*K + // + // A better version using a^-1 + // - v = (a*K*y + a^-1*a*z + b) div K + // = a*y + ((K*A + g)*z + b) div K where we write a*a^-1 = K*A + g + // = a*y + A + (g*z + b) div K + // - k*K <= b Kod m + gz < (k+1)*K + // where k is between 0 and g + // when gcd(a, K) = 1, then there are only two cases. + // + model_based_opt::def model_based_opt::solve_mod_div(unsigned x, unsigned_vector const& _mod_rows, unsigned_vector const& _div_rows, bool compute_def) { + def result; + unsigned_vector div_rows(_div_rows), mod_rows(_mod_rows); + SASSERT(!div_rows.empty() || !mod_rows.empty()); + TRACE("opt", display(tout << "solve_div " << x << "\n")); + + rational K(1); + for (unsigned ri : div_rows) + K = lcm(K, m_rows[ri].m_mod); + for (unsigned ri : mod_rows) + K = lcm(K, m_rows[ri].m_mod); + + rational x_value = m_var2value[x]; + rational z_value = mod(x_value, K); + rational y_value = div(x_value, K); + SASSERT(x_value == K * y_value + z_value); + SASSERT(0 <= z_value && z_value < K); + // add new variables + unsigned z = add_var(z_value, true); + unsigned y = add_var(y_value, true); + + uint_set visited; + unsigned j = 0; + for (unsigned ri : div_rows) { + if (visited.contains(ri)) + continue; + row& r = m_rows[ri]; + mul(ri, K / r.m_mod); + r.m_alive = false; + visited.insert(ri); + div_rows[j++] = ri; + } + div_rows.shrink(j); + + j = 0; + for (unsigned ri : mod_rows) { + if (visited.contains(ri)) + continue; + m_rows[ri].m_alive = false; + visited.insert(ri); + mod_rows[j++] = ri; + } + mod_rows.shrink(j); + + + // replace x by K*y + z in other rows. + for (unsigned ri : m_var2row_ids[x]) { + if (visited.contains(ri)) + continue; + replace_var(ri, x, K, y, rational::one(), z); + visited.insert(ri); + normalize(ri); + } + + // add bounds for z + add_lower_bound(z, rational::zero()); + add_upper_bound(z, K - 1); + + + // solve for x_value = K*y_value + z_value, 0 <= z_value < K. + + unsigned_vector vs; + + for (unsigned ri : div_rows) { + + rational a = get_coefficient(ri, x); + replace_var(ri, x, rational::zero()); + + // add w = b div m + vector coeffs = m_rows[ri].m_vars; + rational coeff = m_rows[ri].m_coeff; + unsigned w = UINT_MAX; + rational offset(0); + if (K == 1) + offset = coeff; + else if (coeffs.empty()) + offset = div(coeff, K); + else + w = add_div(coeffs, coeff, K); + + // + // w = b div K + // v = a*y + w + k + // k = (a*z_value + (b_value mod K)) div K + // k*K <= a*z + b mod K < (k+1)*K + // + /** + * It is based on the following claim (tested for select values of a, K) + * (define-const K Int 13) + * (declare-const b Int) + * (define-const a Int -11) + * (declare-const y Int) + * (declare-const z Int) + * (define-const w Int (div b K)) + * (define-const k1 Int (+ (* a z) (mod b K))) + * (define-const k Int (div k1 K)) + * (define-const x Int (+ (* K y) z)) + * (define-const u Int (+ (* a x) b)) + * (define-const v Int (+ (* a y) w k)) + * (assert (<= 0 z)) + * (assert (< z K)) + * (assert (<= (* K k) k1)) + * (assert (< k1 (* K (+ k 1)))) + * (assert (not (= (div u K) v))) + * (check-sat) + */ + unsigned v = m_rows[ri].m_id; + rational b_value = eval(coeffs) + coeff; + rational k = div(a * z_value + mod(b_value, K), K); + vector div_coeffs; + div_coeffs.push_back(var(v, rational::minus_one())); + div_coeffs.push_back(var(y, a)); + if (w != UINT_MAX) + div_coeffs.push_back(var(w, rational::one())); + else if (K == 1) + div_coeffs.append(coeffs); + add_constraint(div_coeffs, k + offset, t_eq); + + unsigned u = UINT_MAX; + offset = 0; + if (K == 1) + offset = 0; + else if (coeffs.empty()) + offset = mod(coeff, K); + else + u = add_mod(coeffs, coeff, K); + + + // add a*z + (b mod K) < (k + 1)*K + vector bound_coeffs; + bound_coeffs.push_back(var(z, a)); + if (u != UINT_MAX) + bound_coeffs.push_back(var(u, rational::one())); + add_constraint(bound_coeffs, 1 - K * (k + 1) + offset, t_le); + + // add k*K <= az + (b mod K) + for (auto& c : bound_coeffs) + c.m_coeff.neg(); + add_constraint(bound_coeffs, k * K - offset, t_le); + // allow to recycle row. + retire_row(ri); + vs.push_back(v); + } + + for (unsigned ri : mod_rows) { + rational a = get_coefficient(ri, x); + replace_var(ri, x, rational::zero()); + + // add w = b mod K + vector coeffs = m_rows[ri].m_vars; + rational coeff = m_rows[ri].m_coeff; + unsigned v = m_rows[ri].m_id; + rational v_value = m_var2value[v]; + + unsigned w = UINT_MAX; + rational offset(0); + if (coeffs.empty() || K == 1) + offset = mod(coeff, K); + else + w = add_mod(coeffs, coeff, K); + + + rational w_value = w == UINT_MAX ? offset : m_var2value[w]; + + // add v = a*z + w - V, for k = (a*z_value + w_value) div K + // claim: (= (mod x K) (- x (* K (div x K)))))) is a theorem for every x, K != 0 + rational V = v_value - a * z_value - w_value; + vector mod_coeffs; + mod_coeffs.push_back(var(v, rational::minus_one())); + mod_coeffs.push_back(var(z, a)); + if (w != UINT_MAX) mod_coeffs.push_back(var(w, rational::one())); + add_constraint(mod_coeffs, V + offset, t_eq); + add_lower_bound(v, rational::zero()); + add_upper_bound(v, K - 1); + + retire_row(ri); + vs.push_back(v); + } + + + for (unsigned v : vs) + project(v, false); + + // project internal variables. + + def y_def = project(y, compute_def); + def z_def = project(z, compute_def); + + if (compute_def) { + result = (y_def * K) + z_def; + m_var2value[x] = eval(result); + } + TRACE("opt", display(tout << "solve_div done v" << x << "\n")); + return result; + } + + // + // compute D and u. + // + // D = lcm(d1, d2) + // u = eval(x) mod D + // + // d1 | (a1x + t1) & d2 | (a2x + t2) + // = + // d1 | (a1(D*x' + u) + t1) & d2 | (a2(D*x' + u) + t2) + // = + // d1 | (a1*u + t1) & d2 | (a2*u + t2) + // + // x := D*x' + u + // + + model_based_opt::def model_based_opt::solve_divides(unsigned x, unsigned_vector const& divide_rows, bool compute_def) { + SASSERT(!divide_rows.empty()); + rational D(1); + for (unsigned idx : divide_rows) { + D = lcm(D, m_rows[idx].m_mod); + } + if (D.is_zero()) { + throw default_exception("modulo 0 is not defined"); + } + if (D.is_neg()) D = abs(D); + TRACE("opt1", display(tout << "lcm: " << D << " x: v" << x << " tableau\n");); + rational val_x = m_var2value[x]; + rational u = mod(val_x, D); + SASSERT(u.is_nonneg() && u < D); + for (unsigned idx : divide_rows) { + replace_var(idx, x, u); + SASSERT(invariant(idx, m_rows[idx])); + normalize(idx); + } + TRACE("opt1", display(tout << "tableau after replace x under mod\n");); + // + // update inequalities such that u is added to t and + // D is multiplied to coefficient of x. + // the interpretation of the new version of x is (x-u)/D + // + // a*x + t <= 0 + // a*(D*x' + u) + t <= 0 + // a*D*x' + a*u + t <= 0 + // + rational new_val = (val_x - u) / D; + SASSERT(new_val.is_int()); + unsigned y = add_var(new_val, true); + unsigned_vector const& row_ids = m_var2row_ids[x]; + uint_set visited; + for (unsigned row_id : row_ids) { + if (visited.contains(row_id)) + continue; + // x |-> D*y + u + replace_var(row_id, x, D, y, u); + visited.insert(row_id); + normalize(row_id); + } + TRACE("opt1", display(tout << "tableau after replace x by y := v" << y << "\n");); + def result = project(y, compute_def); + if (compute_def) { + result = (result * D) + u; + m_var2value[x] = eval(result); + } + TRACE("opt1", display(tout << "tableau after project y" << y << "\n");); + + return result; + } + + // update row with: x |-> C + void model_based_opt::replace_var(unsigned row_id, unsigned x, rational const& C) { + row& r = m_rows[row_id]; + SASSERT(!get_coefficient(row_id, x).is_zero()); + unsigned sz = r.m_vars.size(); + unsigned i = 0, j = 0; + rational coeff(0); + for (; i < sz; ++i) { + if (r.m_vars[i].m_id == x) { + coeff = r.m_vars[i].m_coeff; + } + else { + if (i != j) { + r.m_vars[j] = r.m_vars[i]; + } + ++j; + } + } + if (j != sz) { + r.m_vars.shrink(j); + } + r.m_coeff += coeff*C; + r.m_value += coeff*(C - m_var2value[x]); + } + + // update row with: x |-> A*y + B + void model_based_opt::replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B) { + row& r = m_rows[row_id]; + rational coeff = get_coefficient(row_id, x); + if (coeff.is_zero()) return; + if (!r.m_alive) return; + replace_var(row_id, x, B); + r.m_vars.push_back(var(y, coeff*A)); + r.m_value += coeff*A*m_var2value[y]; + if (!r.m_vars.empty() && r.m_vars.back().m_id > y) + std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); + m_var2row_ids[y].push_back(row_id); + SASSERT(invariant(row_id, r)); + } + + // update row with: x |-> A*y + B*z + void model_based_opt::replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B, unsigned z) { + row& r = m_rows[row_id]; + rational coeff = get_coefficient(row_id, x); + if (coeff.is_zero() || !r.m_alive) + return; + replace_var(row_id, x, rational::zero()); + if (A != 0) r.m_vars.push_back(var(y, coeff*A)); + if (B != 0) r.m_vars.push_back(var(z, coeff*B)); + r.m_value += coeff*A*m_var2value[y]; + r.m_value += coeff*B*m_var2value[z]; + std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); + if (A != 0) m_var2row_ids[y].push_back(row_id); + if (B != 0) m_var2row_ids[z].push_back(row_id); + SASSERT(invariant(row_id, r)); + } + + // 3x + t = 0 & 7 | (c*x + s) & ax <= u + // 3 | -t & 21 | (-ct + 3s) & a-t <= 3u + + model_based_opt::def model_based_opt::solve_for(unsigned row_id1, unsigned x, bool compute_def) { + TRACE("opt", tout << "v" << x << " := " << eval(x) << "\n" << m_rows[row_id1] << "\n"; + display(tout)); + rational a = get_coefficient(row_id1, x), b; + row& r1 = m_rows[row_id1]; + ineq_type ty = r1.m_type; + SASSERT(!a.is_zero()); + SASSERT(r1.m_alive); + if (a.is_neg()) { + a.neg(); + r1.neg(); + } + SASSERT(a.is_pos()); + if (ty == t_lt) { + SASSERT(compute_def); + r1.m_coeff -= r1.m_value; + r1.m_type = t_le; + r1.m_value = 0; + } + + if (m_var2is_int[x] && !a.is_one()) { + r1.m_coeff -= r1.m_value; + r1.m_value = 0; + vector coeffs; + mk_coeffs_without(coeffs, r1.m_vars, x); + rational c = mod(-eval(coeffs), a); + add_divides(coeffs, c, a); + } + unsigned_vector const& row_ids = m_var2row_ids[x]; + uint_set visited; + visited.insert(row_id1); + for (unsigned row_id2 : row_ids) { + if (visited.contains(row_id2)) + continue; + visited.insert(row_id2); + row& r = m_rows[row_id2]; + if (!r.m_alive) + continue; + b = get_coefficient(row_id2, x); + if (b.is_zero()) + continue; + row& dst = m_rows[row_id2]; + switch (dst.m_type) { + case t_eq: + case t_lt: + case t_le: + solve(row_id1, a, row_id2, x); + break; + case t_divides: + case t_mod: + case t_div: + // mod reduction already done. + UNREACHABLE(); + break; + } + } + def result; + if (compute_def) { + result = def(m_rows[row_id1], x); + m_var2value[x] = eval(result); + TRACE("opt1", tout << "updated eval " << x << " := " << eval(x) << "\n";); + } + retire_row(row_id1); + TRACE("opt", display(tout << "solved v" << x << "\n")); + return result; + } + + vector model_based_opt::project(unsigned num_vars, unsigned const* vars, bool compute_def) { + vector result; + for (unsigned i = 0; i < num_vars; ++i) { + result.push_back(project(vars[i], compute_def)); + TRACE("opt", display(tout << "After projecting: v" << vars[i] << "\n");); + } + return result; + } + +} + diff --git a/src/math/simplex/model_based_opt.h b/src/math/simplex/model_based_opt.h index cb8c18542..5ba9bd619 100644 --- a/src/math/simplex/model_based_opt.h +++ b/src/math/simplex/model_based_opt.h @@ -30,7 +30,9 @@ namespace opt { t_eq, t_lt, t_le, - t_mod + t_divides, + t_mod, + t_div }; @@ -48,15 +50,23 @@ namespace opt { return x.m_id < y.m_id; } }; + + bool operator==(var const& other) const { + return m_id == other.m_id && m_coeff == other.m_coeff; + } + + bool operator!=(var const& other) const { + return !(*this == other); + } }; struct row { - row(): m_type(t_le), m_value(0), m_alive(false) {} - vector m_vars; // variables with coefficients - rational m_coeff; // constant in inequality - rational m_mod; // value the term divide - ineq_type m_type; // inequality type - rational m_value; // value of m_vars + m_coeff under interpretation of m_var2value. - bool m_alive; // rows can be marked dead if they have been processed. + vector m_vars; // variables with coefficients + rational m_coeff = rational::zero(); // constant in inequality + rational m_mod = rational::zero(); // value the term divide + ineq_type m_type = t_le; // inequality type + rational m_value = rational::zero(); // value of m_vars + m_coeff under interpretation of m_var2value. + bool m_alive = false; // rows can be marked dead if they have been processed. + unsigned m_id = UINT_MAX; // variable defined by row (used for mod_t and div_t) void reset() { m_vars.reset(); m_coeff.reset(); m_value.reset(); } row& normalize(); @@ -85,9 +95,9 @@ namespace opt { static const unsigned m_objective_id = 0; vector m_var2row_ids; vector m_var2value; - bool_vector m_var2is_int; + bool_vector m_var2is_int; vector m_new_vars; - unsigned_vector m_lub, m_glb, m_mod; + unsigned_vector m_lub, m_glb, m_divides, m_mod, m_div; unsigned_vector m_above, m_below; unsigned_vector m_retired_rows; @@ -114,7 +124,7 @@ namespace opt { void mul_add(bool same_sign, unsigned row_id1, rational const& c, unsigned row_id2); - void mul_add(unsigned x, rational const& a1, unsigned row_src, rational const& a2, unsigned row_dst); + void mul_add(unsigned x, rational a1, unsigned row_src, rational a2, unsigned row_dst); void mul(unsigned dst, rational const& c); @@ -122,14 +132,18 @@ namespace opt { void sub(unsigned dst, rational const& c); - void del_var(unsigned dst, unsigned x); + void set_row(unsigned row_id, vector const& coeffs, rational const& c, rational const& m, ineq_type rel); - void set_row(unsigned row_id, vector const& coeffs, rational const& c, rational const& m, ineq_type rel); + void add_lower_bound(unsigned x, rational const& lo); - void add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type r); + void add_upper_bound(unsigned x, rational const& hi); + + unsigned add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type r, unsigned id); void replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B); + void replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B, unsigned z); + void replace_var(unsigned row_id, unsigned x, rational const& C); void normalize(unsigned row_id); @@ -138,7 +152,7 @@ namespace opt { unsigned new_row(); - unsigned copy_row(unsigned row_id); + unsigned copy_row(unsigned row_id, unsigned excl = UINT_MAX); rational n_sign(rational const& b) const; @@ -150,8 +164,10 @@ namespace opt { def solve_for(unsigned row_id, unsigned x, bool compute_def); - def solve_mod(unsigned x, unsigned_vector const& mod_rows, bool compute_def); - + def solve_divides(unsigned x, unsigned_vector const& divide_rows, bool compute_def); + + def solve_mod_div(unsigned x, unsigned_vector const& mod_rows, unsigned_vector const& divide_rows, bool compute_def); + bool is_int(unsigned x) const { return m_var2is_int[x]; } void retire_row(unsigned row_id); @@ -173,6 +189,17 @@ namespace opt { // add a divisibility constraint. The row should divide m. void add_divides(vector const& coeffs, rational const& c, rational const& m); + + // add sub-expression for modulus + // v = add_mod(coeffs, m) means + // v = coeffs mod m + unsigned add_mod(vector const& coeffs, rational const& c, rational const& m); + + // add sub-expression for div + // v = add_div(coeffs, m) means + // v = coeffs div m + unsigned add_div(vector const& coeffs, rational const& c, rational const& m); + // Set the objective function (linear). void set_objective(vector const& coeffs, rational const& c); diff --git a/src/math/simplex/network_flow.h b/src/math/simplex/network_flow.h index 9be3b1191..b2c9cd9b3 100644 --- a/src/math/simplex/network_flow.h +++ b/src/math/simplex/network_flow.h @@ -87,7 +87,7 @@ namespace smt { m_states(states), m_enter_id(enter_id) { } - virtual ~pivot_rule_impl() {} + virtual ~pivot_rule_impl() = default; virtual bool choose_entering_edge() = 0; virtual pivot_rule rule() const = 0; }; diff --git a/src/math/simplex/simplex.cpp b/src/math/simplex/simplex.cpp index 643db9c5b..6174b81b3 100644 --- a/src/math/simplex/simplex.cpp +++ b/src/math/simplex/simplex.cpp @@ -41,6 +41,10 @@ namespace simplex { sparse_matrix_ops::kernel(M, K); } + void kernel_ffe(sparse_matrix &M, vector> &K) { + sparse_matrix_ops::kernel_ffe(M, K); + } + void ensure_rational_solution(simplex& S) { rational delta(1); for (unsigned i = 0; i < S.get_num_vars(); ++i) { diff --git a/src/math/simplex/simplex.h b/src/math/simplex/simplex.h index 8c470a242..0405a59d0 100644 --- a/src/math/simplex/simplex.h +++ b/src/math/simplex/simplex.h @@ -203,5 +203,6 @@ namespace simplex { void ensure_rational_solution(simplex& s); void kernel(sparse_matrix& s, vector>& K); + void kernel_ffe(sparse_matrix &s, vector> &K); }; diff --git a/src/math/simplex/sparse_matrix.h b/src/math/simplex/sparse_matrix.h index ecba27af5..9333e9ddb 100644 --- a/src/math/simplex/sparse_matrix.h +++ b/src/math/simplex/sparse_matrix.h @@ -168,6 +168,7 @@ namespace simplex { void add_var(row r, numeral const& n, var_t var); void add(row r, numeral const& n, row src); void mul(row r, numeral const& n); + void div(row r, numeral const& n); void neg(row r); void del(row r); diff --git a/src/math/simplex/sparse_matrix_def.h b/src/math/simplex/sparse_matrix_def.h index 255e62f11..914ab1d94 100644 --- a/src/math/simplex/sparse_matrix_def.h +++ b/src/math/simplex/sparse_matrix_def.h @@ -432,6 +432,25 @@ namespace simplex { } } + /** + \brief Set row <- n/row + */ + template + void sparse_matrix::div(row r, numeral const &n) { + SASSERT(!m.is_zero(n)); + if (m.is_one(n)) { + // no op + } else if (m.is_minus_one(n)) { + neg(r); + } else { + row_iterator it = row_begin(r); + row_iterator end = row_end(r); + for (; it != end; ++it) { + m.div(it->m_coeff, n, it->m_coeff); + } + } + } + /** \brief Delete row. */ diff --git a/src/math/simplex/sparse_matrix_ops.h b/src/math/simplex/sparse_matrix_ops.h index 039d2fede..8cc1fdd3e 100644 --- a/src/math/simplex/sparse_matrix_ops.h +++ b/src/math/simplex/sparse_matrix_ops.h @@ -43,7 +43,7 @@ class sparse_matrix_ops { for (auto [row, row_entry] : M.get_rows(k)) { if (c[row.id()] != 0) continue; auto &m_jk = row_entry->m_coeff; - if (mpq_manager::is_zero(m_jk)) continue; + if (m.is_zero(m_jk)) continue; // D = rational(-1) / m_jk; m.set(D, m_jk); @@ -68,9 +68,10 @@ class sparse_matrix_ops { K.push_back(vector()); for (unsigned i = 0; i < n_vars; ++i) { if (d[i] > 0) { - auto r = sparse_matrix::row(d[i] - 1); + auto r = typename sparse_matrix::row(d[i] - 1); K.back().push_back(rational(M.get_coeff(r, k))); - } else if (i == k) + } + else if (i == k) K.back().push_back(rational(1)); else K.back().push_back(rational(0)); @@ -81,5 +82,157 @@ class sparse_matrix_ops { static void kernel(sparse_matrix &M, vector> &K) { kernel(M, K); } + + /// \brief Kernel computation using fraction-free-elimination + /// + template + static void kernel_ffe(sparse_matrix &M, vector> &K) { + using scoped_numeral = typename Ext::scoped_numeral; + + /// Based on George Nakos, Peter R. Turner, Robert M. Williams: + /// Fraction-free algorithms for linear and polynomial equations. SIGSAM + /// Bull. 31(3): 11-19 (1997) + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral m_jk(m); + scoped_numeral last_pv(m); + + m.set(last_pv, 1); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) continue; + auto &m_jk_ref = row_entry->m_coeff; + if (m.is_zero(m_jk_ref)) + // XXX: should not happen, the matrix is sparse + continue; + + // this a pivot column + m.set(m_jk, m_jk_ref); + + // ensure that pivot is negative + if (m.is_pos(m_jk_ref)) { M.neg(row); } + else { m.neg(m_jk); } + // m_jk is abs(M[j]][k]) + + for (auto row_i : M.get_rows()) { + if (row_i.id() == row.id()) continue; + + m.set(m_ik, M.get_coeff(row_i, k)); + // row_i *= m_jk + M.mul(row_i, m_jk); + if (!m.is_zero(m_ik)) { + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + M.div(row_i, last_pv); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + m.set(last_pv, m_jk); + break; + } + } + + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) continue; + K.push_back(vector()); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.back().push_back(rational(M.get_coeff(r, k))); + } + else if (i == k) + K.back().push_back(rational(last_pv)); + else + K.back().push_back(rational(0)); + } + } + } + + static void kernel_ffe(sparse_matrix &M, + vector> &K) { + kernel_ffe(M, K); + } + + + template + static void kernel_ffe(sparse_matrix &M, sparse_matrix &K, + vector &basics) { + using scoped_numeral = typename Ext::scoped_numeral; + + /// Based on George Nakos, Peter R. Turner, Robert M. Williams: + /// Fraction-free algorithms for linear and polynomial equations. SIGSAM + /// Bull. 31(3): 11-19 (1997) + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral m_jk(m); + scoped_numeral last_pv(m); + + m.set(last_pv, 1); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) continue; + auto &m_jk_ref = row_entry->m_coeff; + if (m.is_zero(m_jk_ref)) + // XXX: should not happen, the matrix is sparse + continue; + + // this a pivot column + m.set(m_jk, m_jk_ref); + + // ensure that pivot is negative + if (m.is_pos(m_jk_ref)) { M.neg(row); } + else { m.neg(m_jk); } + // m_jk is abs(M[j]][k]) + + for (auto row_i : M.get_rows()) { + if (row_i.id() == row.id()) continue; + + m.set(m_ik, M.get_coeff(row_i, k)); + // row_i *= m_jk + M.mul(row_i, m_jk); + if (!m.is_zero(m_ik)) { + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + M.div(row_i, last_pv); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + m.set(last_pv, m_jk); + break; + } + } + + K.ensure_var(n_vars - 1); + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) continue; + auto row = K.mk_row(); + basics.push_back(k); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.add_var(row, M.get_coeff(r, k), i); + } + else if (i == k) + K.add_var(row, last_pv, i); + } + } + } + }; } // namespace simplex diff --git a/src/math/subpaving/subpaving.h b/src/math/subpaving/subpaving.h index 96d05f4de..b76f5e831 100644 --- a/src/math/subpaving/subpaving.h +++ b/src/math/subpaving/subpaving.h @@ -39,7 +39,7 @@ namespace subpaving { class context { public: - virtual ~context() {} + virtual ~context() = default; virtual unsynch_mpq_manager & qm() const = 0; diff --git a/src/math/subpaving/subpaving_t.h b/src/math/subpaving/subpaving_t.h index cfe3aea70..7300e3da3 100644 --- a/src/math/subpaving/subpaving_t.h +++ b/src/math/subpaving/subpaving_t.h @@ -385,7 +385,7 @@ public: context_t * m_ctx; public: node_selector(context_t * ctx):m_ctx(ctx) {} - virtual ~node_selector() {} + virtual ~node_selector() = default; context_t * ctx() const { return m_ctx; } @@ -403,7 +403,7 @@ public: context_t * m_ctx; public: var_selector(context_t * ctx):m_ctx(ctx) {} - virtual ~var_selector() {} + virtual ~var_selector() = default; context_t * ctx() const { return m_ctx; } @@ -436,7 +436,7 @@ public: context_t * m_ctx; public: node_splitter(context_t * ctx):m_ctx(ctx) {} - virtual ~node_splitter() {} + virtual ~node_splitter() = default; context_t * ctx() const { return m_ctx; } node * mk_node(node * p) { return ctx()->mk_node(p); } diff --git a/src/model/datatype_factory.cpp b/src/model/datatype_factory.cpp index 56312839a..c01b385d7 100644 --- a/src/model/datatype_factory.cpp +++ b/src/model/datatype_factory.cpp @@ -87,7 +87,7 @@ expr * datatype_factory::get_almost_fresh_value(sort * s) { return val; } // Traverse constructors, and try to invoke get_fresh_value of one of the arguments (if the argument is not a sibling datatype of s). - // If the argumet is a sibling datatype of s, then + // If the argument is a sibling datatype of s, then // use get_last_fresh_value. ptr_vector const & constructors = *m_util.get_datatype_constructors(s); for (func_decl * constructor : constructors) { diff --git a/src/model/model.h b/src/model/model.h index 822e4cff9..07049a522 100644 --- a/src/model/model.h +++ b/src/model/model.h @@ -81,6 +81,8 @@ public: expr_ref get_inlined_const_interp(func_decl* f, bool force_inline); expr_ref unfold_as_array(expr* e); + void set_inline() { m_inline = true; } + // // Primitives for building models // diff --git a/src/model/model_evaluator.cpp b/src/model/model_evaluator.cpp index 5c6d0f311..843345a46 100644 --- a/src/model/model_evaluator.cpp +++ b/src/model/model_evaluator.cpp @@ -158,6 +158,35 @@ struct evaluator_cfg : public default_rewriter_cfg { return st; } + bool contains_as_array(expr* e) { + if (m_ar.is_as_array(e)) + return true; + if (is_var(e)) + return false; + if (is_app(e) && to_app(e)->get_num_args() == 0) + return false; + + struct has_as_array {}; + struct has_as_array_finder { + array_util& au; + has_as_array_finder(array_util& au): au(au) {} + void operator()(var* v) {} + void operator()(quantifier* q) {} + void operator()(app* a) { + if (au.is_as_array(a->get_decl())) + throw has_as_array(); + } + }; + has_as_array_finder ha(m_ar); + try { + for_each_expr(ha, e); + } + catch (has_as_array) { + return true; + } + return false; + } + br_status reduce_app_core(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { result_pr = nullptr; family_id fid = f->get_family_id(); @@ -174,11 +203,17 @@ struct evaluator_cfg : public default_rewriter_cfg { #endif - if (num == 0 && (fid == null_family_id || _is_uninterp || m_ar.is_as_array(f))) { - expr * val = m_model.get_const_interp(f); + func_decl* g = nullptr; + if (num == 0 && m_ar.is_as_array(f, g)) { + auto* fi = m_model.get_func_interp(g); + if (fi && (result = fi->get_array_interp(g))) + return BR_REWRITE1; + } + if (num == 0 && (fid == null_family_id || _is_uninterp)) { + expr* val = m_model.get_const_interp(f); if (val != nullptr) { result = val; - st = m_ar.is_as_array(val) ? BR_REWRITE1 : BR_DONE; + st = contains_as_array(val) ? BR_REWRITE_FULL : BR_DONE; TRACE("model_evaluator", tout << result << "\n";); return st; } @@ -192,6 +227,7 @@ struct evaluator_cfg : public default_rewriter_cfg { result = val; return BR_DONE; } + // fall through } @@ -226,6 +262,11 @@ struct evaluator_cfg : public default_rewriter_cfg { if (st != BR_FAILED) return st; } + if (k == OP_AND) { + st = m_a_rw.mk_and_core(num, args, result); + if (st != BR_FAILED) + return st; + } return m_b_rw.mk_app_core(f, num, args, result); } if (fid == m_a_rw.get_fid()) @@ -566,14 +607,13 @@ struct evaluator_cfg : public default_rewriter_cfg { bool extract_array_func_interp(expr* a, vector& stores, expr_ref& else_case, bool& are_unique) { SASSERT(m_ar.is_array(a)); - bool are_values = true; are_unique = true; TRACE("model_evaluator", tout << mk_pp(a, m) << "\n";); while (m_ar.is_store(a)) { expr_ref_vector store(m); store.append(to_app(a)->get_num_args()-1, to_app(a)->get_args()+1); - are_values &= args_are_values(store, are_unique); + args_are_values(store, are_unique); stores.push_back(store); a = to_app(a)->get_arg(0); } @@ -584,9 +624,8 @@ struct evaluator_cfg : public default_rewriter_cfg { } if (m_ar_rw.has_index_set(a, else_case, stores)) { - for (auto const& store : stores) { - are_values &= args_are_values(store, are_unique); - } + for (auto const& store : stores) + args_are_values(store, are_unique); return true; } if (!m_ar.is_as_array(a)) { diff --git a/src/model/model_macro_solver.h b/src/model/model_macro_solver.h index 1c04f8ec1..8070b4ada 100644 --- a/src/model/model_macro_solver.h +++ b/src/model/model_macro_solver.h @@ -20,7 +20,7 @@ Author: class quantifier2macro_infos { public: - virtual ~quantifier2macro_infos() {} + virtual ~quantifier2macro_infos() = default; virtual quantifier_macro_info* operator()(quantifier* q) = 0; }; @@ -48,7 +48,7 @@ public: m_model(nullptr) { } - virtual ~base_macro_solver() {} + virtual ~base_macro_solver() = default; /** \brief Try to satisfy quantifiers in qs by using macro definitions. diff --git a/src/muz/base/dl_context.cpp b/src/muz/base/dl_context.cpp index f6ce39518..4efe79dd3 100644 --- a/src/muz/base/dl_context.cpp +++ b/src/muz/base/dl_context.cpp @@ -53,7 +53,7 @@ namespace datalog { m_limited_size = ctx.get_decl_util().try_get_size(s, m_size); } public: - virtual ~sort_domain() {} + virtual ~sort_domain() = default; sort_kind get_kind() const { return m_kind; } virtual unsigned get_constant_count() const = 0; diff --git a/src/muz/base/dl_context.h b/src/muz/base/dl_context.h index e9ee4c690..eae846835 100644 --- a/src/muz/base/dl_context.h +++ b/src/muz/base/dl_context.h @@ -305,7 +305,7 @@ namespace datalog { void register_predicate(func_decl * pred, bool named); /** - Restrict reltaions to set of predicates. + Restrict relations to set of predicates. */ void restrict_predicates(func_decl_set const& preds); diff --git a/src/muz/base/dl_engine_base.h b/src/muz/base/dl_engine_base.h index fcf45abf9..64218f7d6 100644 --- a/src/muz/base/dl_engine_base.h +++ b/src/muz/base/dl_engine_base.h @@ -42,7 +42,7 @@ namespace datalog { std::string m_name; public: engine_base(ast_manager& m, char const* name): m(m), m_name(name) {} - virtual ~engine_base() {} + virtual ~engine_base() = default; virtual expr_ref get_answer() = 0; virtual expr_ref get_ground_sat_answer () { diff --git a/src/muz/base/dl_rule_transformer.h b/src/muz/base/dl_rule_transformer.h index 59eb24f55..c446593bd 100644 --- a/src/muz/base/dl_rule_transformer.h +++ b/src/muz/base/dl_rule_transformer.h @@ -89,7 +89,7 @@ namespace datalog { m_can_destratify_negation(can_destratify_negation), m_transformer(nullptr) {} public: - virtual ~plugin() {} + virtual ~plugin() = default; unsigned get_priority() { return m_priority; } bool can_destratify_negation() const { return m_can_destratify_negation; } diff --git a/src/muz/base/fp_params.pyg b/src/muz/base/fp_params.pyg index 098922b1b..57a264768 100644 --- a/src/muz/base/fp_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -169,7 +169,6 @@ def_module_params('fp', ('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'), ('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"), ('spacer.min_level', UINT, 0, 'Minimal level to explore'), - ('spacer.print_json', SYMBOL, '', 'Print pobs tree in JSON format to a given file'), ('spacer.trace_file', SYMBOL, '', 'Log file for progress events'), ('spacer.ctp', BOOL, True, 'Enable counterexample-to-pushing'), ('spacer.use_inc_clause', BOOL, True, 'Use incremental clause to represent trans'), @@ -181,4 +180,10 @@ def_module_params('fp', ('spacer.use_lim_num_gen', BOOL, False, 'Enable limit numbers generalizer to get smaller numbers'), ('spacer.logic', SYMBOL, '', 'SMT-LIB logic to configure internal SMT solvers'), ('spacer.arith.solver', UINT, 2, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'), + ('spacer.global', BOOL, False, 'Enable global guidance'), + ('spacer.gg.concretize', BOOL, True, 'Enable global guidance concretize'), + ('spacer.gg.conjecture', BOOL, True, 'Enable global guidance conjecture'), + ('spacer.gg.subsume', BOOL, True, 'Enable global guidance subsume'), + ('spacer.use_iuc', BOOL, True, 'Enable Interpolating Unsat Core(IUC) for lemma generalization'), + ('spacer.expand_bnd', BOOL, False, 'Enable expand-bound lemma generalization'), )) diff --git a/src/muz/fp/datalog_parser.h b/src/muz/fp/datalog_parser.h index 2369f1d84..e836cbaec 100644 --- a/src/muz/fp/datalog_parser.h +++ b/src/muz/fp/datalog_parser.h @@ -27,7 +27,7 @@ namespace datalog { public: static parser * create(context& ctx, ast_manager & ast_manager); - virtual ~parser() {} + virtual ~parser() = default; virtual bool parse_file(char const * path) = 0; virtual bool parse_string(char const * string) = 0; @@ -37,7 +37,7 @@ namespace datalog { public: static wpa_parser * create(context& ctx, ast_manager & ast_manager); - virtual ~wpa_parser() {} + virtual ~wpa_parser() = default; virtual bool parse_directory(char const * path) = 0; }; diff --git a/src/muz/fp/dl_cmds.cpp b/src/muz/fp/dl_cmds.cpp index 62455b625..f40400167 100644 --- a/src/muz/fp/dl_cmds.cpp +++ b/src/muz/fp/dl_cmds.cpp @@ -226,7 +226,7 @@ public: void set_next_arg(cmd_context & ctx, func_decl* t) override { m_target = t; if (t->get_family_id() != null_family_id) { - throw cmd_exception("Invalid query argument, expected uinterpreted function name, but argument is interpreted"); + throw cmd_exception("Invalid query argument, expected uninterpreted function name, but argument is interpreted"); } datalog::context& dlctx = m_dl_ctx->dlctx(); if (!dlctx.get_predicates().contains(t)) { diff --git a/src/muz/rel/aig_exporter.cpp b/src/muz/rel/aig_exporter.cpp index c8ef1321f..e708c1457 100644 --- a/src/muz/rel/aig_exporter.cpp +++ b/src/muz/rel/aig_exporter.cpp @@ -35,7 +35,7 @@ namespace datalog { // reserve pred id = 0 for initialization purposes unsigned num_preds = (unsigned)predicates.size() + 1; - // poor's man round-up log2 + // poor man's round-up log2 unsigned preds_bitsize = log2(num_preds); if ((1U << preds_bitsize) < num_preds) ++preds_bitsize; diff --git a/src/muz/rel/dl_base.h b/src/muz/rel/dl_base.h index 1a4125ed4..ea317ca45 100644 --- a/src/muz/rel/dl_base.h +++ b/src/muz/rel/dl_base.h @@ -73,7 +73,7 @@ namespace datalog { /** - Termplate class containing common infrastructure for relations and tables + Template class containing common infrastructure for relations and tables */ template struct tr_infrastructure { @@ -179,7 +179,7 @@ namespace datalog { class base_fn { public: base_fn() = default; - virtual ~base_fn() {} + virtual ~base_fn() = default; base_fn(const base_fn &) = delete; base_fn& operator=(const base_fn &) = delete; @@ -260,7 +260,7 @@ namespace datalog { */ bool check_kind(base_object const& r) const { return &r.get_plugin()==this; } public: - virtual ~plugin_object() {} + virtual ~plugin_object() = default; virtual void initialize(family_id fid) { m_kind = fid; } @@ -423,7 +423,7 @@ namespace datalog { } #endif - virtual ~base_ancestor() {} + virtual ~base_ancestor() = default; void set_kind(family_id kind) { SASSERT(kind>=0); m_kind = kind; } @@ -865,7 +865,7 @@ namespace datalog { class table_row_mutator_fn { public: - virtual ~table_row_mutator_fn() {} + virtual ~table_row_mutator_fn() = default; /** \brief The function is called for a particular table row. The \c func_columns contains a pointer to an array of functional column values that can be modified. If the function @@ -879,9 +879,9 @@ namespace datalog { class table_row_pair_reduce_fn { public: - virtual ~table_row_pair_reduce_fn() {} + virtual ~table_row_pair_reduce_fn() = default; /** - \brief The function is called for pair of table rows that became duplicit due to projection. + \brief The function is called for pair of table rows that became duplicated due to projection. The values that are in the first array after return from the function will be used for the resulting row. @@ -1095,7 +1095,7 @@ namespace datalog { unsigned m_ref_cnt; public: iterator_core() : m_ref_cnt(0) {} - virtual ~iterator_core() {} + virtual ~iterator_core() = default; iterator_core(const iterator_core &) = delete; iterator_core & operator=(const iterator_core &) = delete; @@ -1124,7 +1124,7 @@ namespace datalog { unsigned m_ref_cnt; public: row_iterator_core() : m_ref_cnt(0) {} - virtual ~row_iterator_core() {} + virtual ~row_iterator_core() = default; row_iterator_core(const row_iterator_core &) = delete; row_iterator_core & operator=(const row_iterator_core &) = delete; @@ -1205,7 +1205,7 @@ namespace datalog { typedef row_iterator const_iterator; row_interface(const table_base & parent_table) : m_parent_table(parent_table) {} - virtual ~row_interface() {} + virtual ~row_interface() = default; virtual table_element operator[](unsigned col) const = 0; diff --git a/src/muz/rel/dl_external_relation.h b/src/muz/rel/dl_external_relation.h index 4b5689465..17e9a2364 100644 --- a/src/muz/rel/dl_external_relation.h +++ b/src/muz/rel/dl_external_relation.h @@ -26,7 +26,7 @@ namespace datalog { class external_relation_context { public: - virtual ~external_relation_context() {} + virtual ~external_relation_context() = default; virtual family_id get_family_id() const = 0; diff --git a/src/muz/rel/dl_instruction.h b/src/muz/rel/dl_instruction.h index 1edacc94f..a008c2b73 100644 --- a/src/muz/rel/dl_instruction.h +++ b/src/muz/rel/dl_instruction.h @@ -317,7 +317,7 @@ namespace datalog { class instruction_block { public: struct instruction_observer { - virtual ~instruction_observer() {} + virtual ~instruction_observer() = default; virtual void notify(instruction * i) {} }; private: diff --git a/src/muz/rel/dl_lazy_table.h b/src/muz/rel/dl_lazy_table.h index 56bfc961f..9f88e6aa2 100644 --- a/src/muz/rel/dl_lazy_table.h +++ b/src/muz/rel/dl_lazy_table.h @@ -107,7 +107,7 @@ namespace datalog { public: lazy_table_ref(lazy_table_plugin& p, table_signature const& sig): m_plugin(p), m_signature(sig), m_ref(0) {} - virtual ~lazy_table_ref() {} + virtual ~lazy_table_ref() = default; void inc_ref() { ++m_ref; } void dec_ref() { --m_ref; if (0 == m_ref) dealloc(this); } void release_table() { m_table.release(); } diff --git a/src/muz/rel/dl_relation_manager.cpp b/src/muz/rel/dl_relation_manager.cpp index 51e7f4ac6..9410e2ab0 100644 --- a/src/muz/rel/dl_relation_manager.cpp +++ b/src/muz/rel/dl_relation_manager.cpp @@ -999,7 +999,7 @@ namespace datalog { class relation_manager::auxiliary_table_transformer_fn { table_fact m_row; public: - virtual ~auxiliary_table_transformer_fn() {} + virtual ~auxiliary_table_transformer_fn() = default; virtual const table_signature & get_result_signature() const = 0; virtual void modify_fact(table_fact & f) const = 0; @@ -1230,18 +1230,18 @@ namespace datalog { /** - An auixiliary class for functors that perform filtering. It performs the table traversal + An auxiliary class for functors that perform filtering. It performs the table traversal and only asks for each individual row whether it should be removed. When using this class in multiple inheritance, this class should not be inherited publicly - and should be mentioned as last. This should ensure that deteletion of the object will + and should be mentioned as last. This should ensure that deletion of the object will go well when initiated from a pointer to the first ancestor. */ class relation_manager::auxiliary_table_filter_fn { table_fact m_row; svector m_to_remove; public: - virtual ~auxiliary_table_filter_fn() {} + virtual ~auxiliary_table_filter_fn() = default; virtual bool should_remove(const table_fact & f) const = 0; void operator()(table_base & r) { diff --git a/src/muz/rel/dl_sparse_table.cpp b/src/muz/rel/dl_sparse_table.cpp index 9f3304e7a..6c2373d54 100644 --- a/src/muz/rel/dl_sparse_table.cpp +++ b/src/muz/rel/dl_sparse_table.cpp @@ -274,7 +274,7 @@ namespace datalog { key_indexer(unsigned key_len, const unsigned * key_cols) : m_key_cols(key_len, key_cols) {} - virtual ~key_indexer() {} + virtual ~key_indexer() = default; virtual void update(const sparse_table & t) {} diff --git a/src/muz/rel/dl_table_plugin.h b/src/muz/rel/dl_table_plugin.h index 51e54d3ea..d0f239f79 100644 --- a/src/muz/rel/dl_table_plugin.h +++ b/src/muz/rel/dl_table_plugin.h @@ -40,7 +40,7 @@ namespace datalog { class base_fn { public: - virtual ~base_fn() {} + virtual ~base_fn() = default; }; class join_fn : public base_fn { @@ -113,7 +113,7 @@ namespace datalog { base_ancestor(kind k, relation_manager & m, const signature & s) : m_kind(k), m_manager(m), m_signature(s) {} public: - virtual ~base_ancestor() {} + virtual ~base_ancestor() = default; kind get_kind() const { return m_kind; } relation_manager & get_manager() const { return m_manager; } diff --git a/src/muz/spacer/.clang-format b/src/muz/spacer/.clang-format new file mode 100644 index 000000000..d1e129060 --- /dev/null +++ b/src/muz/spacer/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: LLVM +AllowShortFunctionsOnASingleLine: All +IndentWidth: '4' +AllowShortBlocksOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +... diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index d703c5db4..28fbddc7b 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -10,6 +10,7 @@ z3_add_component(spacer spacer_prop_solver.cpp spacer_sym_mux.cpp spacer_util.cpp + spacer_cluster_util.cpp spacer_iuc_solver.cpp spacer_legacy_mbp.cpp spacer_proof_utils.cpp @@ -22,12 +23,19 @@ z3_add_component(spacer spacer_sem_matcher.cpp spacer_quant_generalizer.cpp spacer_arith_generalizers.cpp + spacer_global_generalizer.cpp + spacer_ind_lemma_generalizer.cpp + spacer_expand_bnd_generalizer.cpp + spacer_cluster.cpp spacer_callback.cpp - spacer_json.cpp spacer_iuc_proof.cpp spacer_mbc.cpp spacer_pdr.cpp spacer_sat_answer.cpp + spacer_concretize.cpp + spacer_convex_closure.cpp + spacer_conjecture.cpp + spacer_arith_kernel.cpp COMPONENT_DEPENDENCIES arith_tactics core_tactics diff --git a/src/muz/spacer/spacer_antiunify.cpp b/src/muz/spacer/spacer_antiunify.cpp index d51d240b0..11fe80f60 100644 --- a/src/muz/spacer/spacer_antiunify.cpp +++ b/src/muz/spacer/spacer_antiunify.cpp @@ -155,6 +155,7 @@ void anti_unifier::operator()(expr *e1, expr *e2, expr_ref &res, m_pinned.push_back(u); m_cache.insert(n1, n2, u); } + m_todo.pop_back(); } expr *r; diff --git a/src/muz/spacer/spacer_arith_kernel.cpp b/src/muz/spacer/spacer_arith_kernel.cpp new file mode 100644 index 000000000..fdcb6b0d6 --- /dev/null +++ b/src/muz/spacer/spacer_arith_kernel.cpp @@ -0,0 +1,108 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_arith_kernel.cpp + +Abstract: + + Compute kernel of a matrix + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "muz/spacer/spacer_arith_kernel.h" + +#include "math/simplex/sparse_matrix_def.h" +#include "math/simplex/sparse_matrix_ops.h" + +using namespace spacer; + +bool spacer_arith_kernel::compute_kernel() { + SASSERT(m_matrix.num_rows() > 1); + + if (false && m_matrix.compute_linear_deps(m_kernel)) { + // the matrix cannot be reduced further + if (m_matrix.num_cols() - m_kernel.num_rows() <= 1) return true; + + m_kernel.reset(m_kernel.num_cols()); + SASSERT(m_matrix.num_cols() > 2); + } + if (m_matrix.num_cols() > 2) m_st.m_failed++; + if (m_plugin /* && m_matrix.num_cols() > 2 */) { + return m_plugin->compute_kernel(m_matrix, m_kernel, m_basic_vars); + } + return false; +} + +namespace { +class simplex_arith_kernel_plugin : public spacer_arith_kernel::plugin { + public: + simplex_arith_kernel_plugin() {} + + bool compute_kernel(const spacer_matrix &in, spacer_matrix &out, + vector &basics) override { + using qmatrix = simplex::sparse_matrix; + unsynch_mpq_manager m; + qmatrix qmat(m); + + // extra column for column of 1 + qmat.ensure_var(in.num_cols()); + + for (unsigned i = 0, n_rows = in.num_rows(); i < n_rows; ++i) { + auto row_id = qmat.mk_row(); + unsigned j, n_cols; + for (j = 0, n_cols = in.num_cols(); j < n_cols; ++j) { + qmat.add_var(row_id, in.get(i, j).to_mpq(), j); + } + qmat.add_var(row_id, rational::one().to_mpq(), n_cols); + } + TRACE("gg", qmat.display(tout);); + + qmatrix kern(m); + simplex::sparse_matrix_ops::kernel_ffe(qmat, kern, + basics); + + out.reset(kern.num_vars()); + vector vec; + for (auto row : kern.get_rows()) { + vec.reset(); + vec.reserve(kern.num_vars(), rational(0)); + for (auto &[coeff, v] : kern.get_row(row)) { + vec[v] = rational(coeff); + } + out.add_row(vec); + } + + TRACE("gg", { + tout << "Computed kernel\n"; + qmat.display(tout); + tout << "\n"; + kern.display(tout); + tout << "\n"; + tout << "basics: " << basics << "\n"; + out.display(tout); + }); + return out.num_rows() > 0; + } + + void collect_statistics(statistics &st) const override {} + void reset_statistics() override {} + void reset() override {} +}; + +} // namespace + +namespace spacer { + +spacer_arith_kernel::plugin *mk_simplex_kernel_plugin() { + return alloc(simplex_arith_kernel_plugin); +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_arith_kernel.h b/src/muz/spacer/spacer_arith_kernel.h new file mode 100644 index 000000000..683fed2ba --- /dev/null +++ b/src/muz/spacer/spacer_arith_kernel.h @@ -0,0 +1,93 @@ +#pragma once +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_arith_kernel.cpp + +Abstract: + + Compute kernel of a matrix + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "spacer_matrix.h" +#include "util/statistics.h" +namespace spacer { + +/** + Computes a kernel of a matrix. +*/ +class spacer_arith_kernel { + public: + class plugin { + public: + virtual ~plugin() {} + virtual bool compute_kernel(const spacer_matrix &in_matrix, + spacer_matrix &out_kernel, + vector &basics) = 0; + virtual void collect_statistics(statistics &st) const = 0; + virtual void reset_statistics() = 0; + virtual void reset() = 0; + }; + + protected: + struct stats { + unsigned m_failed; + stats() { reset(); } + void reset() { m_failed = 0; } + }; + stats m_st; + + /// Input matrix for which kernel is to be computed + const spacer_matrix &m_matrix; + + /// Output matrix representing the kernel + spacer_matrix m_kernel; + /// columns in the kernel that correspond to basic vars + vector m_basic_vars; + + scoped_ptr m_plugin; + + public: + spacer_arith_kernel(spacer_matrix &matrix) + : m_matrix(matrix), m_kernel(0, 0) {} + virtual ~spacer_arith_kernel() = default; + + void set_plugin(spacer_arith_kernel::plugin *plugin) { m_plugin = plugin; } + + /// Computes kernel of a matrix + /// returns true if the computation was successful + /// use \p spacer_arith_kernel::get_kernel() to get the kernel + bool compute_kernel(); + bool operator()() { return compute_kernel(); } + + const spacer_matrix &get_kernel() const { return m_kernel; } + const vector &get_basic_vars() const { return m_basic_vars; } + + void reset() { + m_kernel = spacer_matrix(0, 0); + if (m_plugin) m_plugin->reset(); + } + + virtual void collect_statistics(statistics &st) const { + st.update("SPACER arith kernel failed", m_st.m_failed); + if (m_plugin) { m_plugin->collect_statistics(st); } + } + virtual void reset_statistics() { + m_st.reset(); + if (m_plugin) m_plugin->reset_statistics(); + } +}; + +spacer_arith_kernel::plugin *mk_simplex_kernel_plugin(); + +} // namespace spacer diff --git a/src/muz/spacer/spacer_cluster.cpp b/src/muz/spacer/spacer_cluster.cpp new file mode 100644 index 000000000..13ef17c26 --- /dev/null +++ b/src/muz/spacer/spacer_cluster.cpp @@ -0,0 +1,397 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster.cpp + +Abstract: + + Discover and mark lemma clusters + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include + +#include "ast/arith_decl_plugin.h" +#include "ast/ast_util.h" +#include "ast/rewriter/var_subst.h" +#include "ast/substitution/substitution.h" +#include "muz/spacer/spacer_antiunify.h" +#include "muz/spacer/spacer_cluster.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_util.h" +#include "smt/tactic/unit_subsumption_tactic.h" +#include "util/mpq.h" +#include "util/vector.h" + +#define MAX_CLUSTER_SIZE 5 +#define MAX_CLUSTERS 5 +#define GAS_INIT 10 + +namespace spacer { + +using var_offset = std::pair; + +lemma_cluster::lemma_cluster(const expr_ref &pattern) + : m(pattern.get_manager()), m_arith(m), m_bv(m), m_ref_count(0), + m_pattern(pattern), m_matcher(m), m_gas(GAS_INIT) { + m_num_vars = get_num_vars(m_pattern); +} + +lemma_cluster::lemma_cluster(const lemma_cluster &other) + : m(other.get_manager()), m_arith(m), m_bv(m), m_ref_count(0), + m_pattern(other.get_pattern()), m_num_vars(other.m_num_vars), + m_matcher(m), m_gas(other.get_gas()) { + for (const auto &li : other.get_lemmas()) { m_lemma_vec.push_back(li); } +} + +/// Get a conjunction of all the lemmas in cluster +void lemma_cluster::get_conj_lemmas(expr_ref &e) const { + expr_ref_vector conj(m); + for (const auto &lem : get_lemmas()) { + conj.push_back(lem.get_lemma()->get_expr()); + } + e = mk_and(conj); +} + +bool lemma_cluster::contains(const lemma_ref &lemma) { + for (const auto &li : get_lemmas()) { + if (lemma->get_expr() == li.get_lemma()->get_expr()) return true; + } + return false; +} + +unsigned lemma_cluster::get_min_lvl() { + if (m_lemma_vec.empty()) return 0; + unsigned lvl = m_lemma_vec[0].get_lemma()->level(); + for (auto l : m_lemma_vec) { lvl = std::min(lvl, l.get_lemma()->level()); } + // if all lemmas are at infinity, use the level of the lowest pob + if (is_infty_level(lvl)) { + for (auto l : m_lemma_vec) { + if (l.get_lemma()->has_pob()) + lvl = std::min(lvl, l.get_lemma()->get_pob()->level()); + } + } + return lvl; +} + +/// Checks whether \p e matches the pattern of the cluster +/// Returns true on success and set \p sub to the corresponding substitution +bool lemma_cluster::match(const expr_ref &e, substitution &sub) { + bool pos; + var_offset var; + expr_offset r; + + m_matcher.reset(); + bool is_match = m_matcher(m_pattern, e, sub, pos); + if (!(is_match && pos)) return false; + + unsigned n_binds = sub.get_num_bindings(); + auto is_numeral = [&](expr *e) { + return m_arith.is_numeral(e) || m_bv.is_numeral(e); + }; + // All the matches should be numerals + for (unsigned i = 0; i < n_binds; i++) { + sub.get_binding(i, var, r); + if (!is_numeral(r.get_expr())) return false; + } + return true; +} + +bool lemma_cluster::can_contain(const lemma_ref &lemma) { + substitution sub(m); + expr_ref cube(m); + + sub.reserve(1, m_num_vars); + cube = mk_and(lemma->get_cube()); + normalize_order(cube, cube); + return match(cube, sub); +} + +lemma_cluster::lemma_info * +lemma_cluster::get_lemma_info(const lemma_ref &lemma) { + SASSERT(contains(lemma)); + for (auto &li : m_lemma_vec) { + if (lemma == li.get_lemma()) { return &li; } + } + UNREACHABLE(); + return nullptr; +} + +/// Removes subsumed lemmas in the cluster +/// +/// Removed lemmas are placed into \p removed_lemmas +void lemma_cluster::rm_subsumed(lemma_info_vector &removed_lemmas) { + removed_lemmas.reset(); + if (m_lemma_vec.size() <= 1) return; + + // set up and run the simplifier + tactic_ref simplifier = mk_unit_subsumption_tactic(m); + goal_ref g(alloc(goal, m, false, false, false)); + goal_ref_buffer result; + for (auto l : m_lemma_vec) { g->assert_expr(l.get_lemma()->get_expr()); } + (*simplifier)(g, result); + + SASSERT(result.size() == 1); + goal *r = result[0]; + + // nothing removed + if (r->size() == m_lemma_vec.size()) return; + + // collect removed lemmas + lemma_info_vector keep; + for (auto lem : m_lemma_vec) { + bool found = false; + for (unsigned i = 0; i < r->size(); i++) { + if (lem.get_lemma()->get_expr() == r->form(i)) { + found = true; + keep.push_back(lem); + TRACE("cluster_stats_verb", tout << "Keeping lemma " + << lem.get_lemma()->get_cube() + << "\n";); + break; + } + } + if (!found) { + TRACE("cluster_stats_verb", tout << "Removing subsumed lemma " + << lem.get_lemma()->get_cube() + << "\n";); + removed_lemmas.push_back(lem); + } + } + m_lemma_vec.reset(); + m_lemma_vec.append(keep); +} + +/// A a lemma to a cluster +/// +/// Removes subsumed lemmas if \p subs_check is true +/// +/// Returns false if lemma does not match the pattern or if it is already in the +/// cluster. Repetition of lemmas is avoided by doing a linear scan over the +/// lemmas in the cluster. Adding a lemma can reduce the size of the cluster due +/// to subsumption reduction. +bool lemma_cluster::add_lemma(const lemma_ref &lemma, bool subsume) { + substitution sub(m); + expr_ref cube(m); + + sub.reserve(1, m_num_vars); + cube = mk_and(lemma->get_cube()); + normalize_order(cube, cube); + + if (!match(cube, sub)) return false; + + // cluster already contains the lemma + if (contains(lemma)) return false; + + TRACE("cluster_stats_verb", + tout << "Trying to add lemma " << lemma->get_cube() << "\n";); + + lemma_cluster::lemma_info li(lemma, sub); + m_lemma_vec.push_back(li); + + if (subsume) { + lemma_info_vector removed_lemmas; + rm_subsumed(removed_lemmas); + for (auto rm : removed_lemmas) { + // There is going to at most one removed lemma that matches l_i + // if there is one, return false since the new lemma was not added + if (rm.get_lemma() == li.get_lemma()) return false; + } + } + TRACE("cluster_stats", tout << "Added lemma\n" << mk_and(lemma->get_cube()) << "\n" + << "to existing cluster\n" << m_pattern << "\n";); + return true; +} + +lemma_cluster_finder::lemma_cluster_finder(ast_manager &_m) + : m(_m), m_arith(m), m_bv(m) {} + +/// Check whether \p cube and \p lcube differ only in interpreted constants +bool lemma_cluster_finder::are_neighbours(const expr_ref &cube1, + const expr_ref &cube2) { + SASSERT(is_ground(cube1)); + SASSERT(is_ground(cube2)); + + anti_unifier antiunify(m); + expr_ref pat(m); + substitution sub1(m), sub2(m); + + antiunify(cube1, cube2, pat, sub1, sub2); + SASSERT(sub1.get_num_bindings() == sub2.get_num_bindings()); + return is_numeric_sub(sub1) && is_numeric_sub(sub2); +} + +/// Compute antiunification of \p cube with all formulas in \p fmls. +/// +/// Should return +/// \exist res (\forall f \in fmls (\exist i_sub res[i_sub] == f)) +/// However, the algorithm is incomplete: it returns such a res iff +/// res \in {antiU(cube, e) | e \in fmls} +/// Returns true if res is found +/// TODO: do complete n-ary anti-unification. Not done now +/// because anti_unifier does not support free variables +bool lemma_cluster_finder::anti_unify_n_intrp(const expr_ref &cube, + expr_ref_vector &fmls, + expr_ref &res) { + expr_ref_vector patterns(m); + expr_ref pat(m); + anti_unifier antiunify(m); + substitution sub1(m), sub2(m); + + TRACE("cluster_stats_verb", + tout << "Trying to generate a general pattern for " << cube + << " neighbours are " << fmls << "\n";); + + // collect candidates for res + for (expr *c : fmls) { + antiunify.reset(); + sub1.reset(); + sub2.reset(); + + SASSERT(are_neighbours(cube, {c, m})); + antiunify(cube, expr_ref(c, m), pat, sub1, sub2); + patterns.push_back(pat); + } + + // go through all the patterns to see if there is a pattern which is general + // enough to include all lemmas. + bool is_general_pattern = false, pos = true, all_same = true; + sem_matcher matcher(m); + unsigned n_vars_pat = 0; + for (expr *e : patterns) { + TRACE("cluster_stats_verb", + tout << "Checking pattern " << mk_pp(e, m) << "\n";); + is_general_pattern = true; + n_vars_pat = get_num_vars(e); + all_same = all_same && n_vars_pat == 0; + for (auto *lcube : fmls) { + matcher.reset(); + sub1.reset(); + sub1.reserve(1, n_vars_pat); + if (!(matcher(e, lcube, sub1, pos) && pos)) { + // this pattern is no good + is_general_pattern = false; + break; + } + } + if (is_general_pattern) { + SASSERT(e != nullptr); + TRACE("cluster_stats", + tout << "Found a general pattern\n" << mk_pp(e, m) << "\n";); + // found a good pattern + res = expr_ref(e, m); + return true; + } + } + + CTRACE("cluster_stats", !all_same, + tout << "Failed to find a general pattern for cluster. Cube is: " + << cube << " Patterns are " << patterns << "\n";); + return false; +} + +/// Add a new lemma \p lemma to a cluster +/// +/// Creates a new cluster for the lemma if necessary +void lemma_cluster_finder::cluster(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + pred_transformer &pt = (lemma->get_pob())->pt(); + + // check whether lemmas has already been added + if (pt.clstr_contains(lemma)) return; + + /// Add the lemma to a cluster it is matched against + lemma_cluster *clstr = pt.clstr_match(lemma); + if (clstr && clstr->get_size() <= MAX_CLUSTER_SIZE) { + TRACE("cluster_stats_verb", { + tout << "Trying to add lemma\n" << lemma->get_cube() + << " to an existing cluster\n"; + for (auto lem : clstr->get_lemmas()) + tout << lem.get_lemma()->get_cube() << "\n"; + }); + clstr->add_lemma(lemma); + return; + } + + /// Dont create more than MAX_CLUSTERS number of clusters + if (clstr && pt.clstr_count(clstr->get_pattern()) > MAX_CLUSTERS) { + return; + } + + // Try to create a new cluster with lemma even if it can belong to an + // oversized cluster. The new cluster will not contain any lemma that is + // already in another cluster. + lemma_ref_vector all_lemmas; + pt.get_all_lemmas(all_lemmas, false); + + expr_ref lcube(m), cube(m); + lcube = mk_and(lemma->get_cube()); + normalize_order(lcube, lcube); + + expr_ref_vector lma_cubes(m); + lemma_ref_vector neighbours; + + for (auto *l : all_lemmas) { + cube.reset(); + cube = mk_and(l->get_cube()); + normalize_order(cube, cube); + // make sure that l is not in any other clusters + if (are_neighbours(lcube, cube) && cube != lcube && + !pt.clstr_contains(l)) { + neighbours.push_back(l); + lma_cubes.push_back(cube); + } + } + + if (neighbours.empty()) return; + + // compute the most general pattern to which lemmas fit + expr_ref pattern(m); + bool is_cluster = anti_unify_n_intrp(lcube, lma_cubes, pattern); + + // no general pattern + if (!is_cluster || get_num_vars(pattern) == 0) return; + + // When creating a cluster, its size can be more than MAX_CLUSTER_SIZE. The + // size limitation is only for adding new lemmas to the cluster. The size is + // just an arbitrary number. + // What matters is that we do not allow a cluster to grow indefinitely. + // for example, given a cluster in which one lemma subsumes all other + // lemmas. No matter how big the cluster is, GSpacer is going to produce the + // exact same pob on this cluster. This can lead to divergence. The + // subsumption check we do is based on unit propagation, it is not complete. + lemma_cluster *cluster = pt.mk_cluster(pattern); + + TRACE("cluster_stats", + tout << "created new cluster with pattern:\n" << pattern << "\n" + << " and lemma cube:\n" << lcube << "\n";); + + IF_VERBOSE(2, verbose_stream() << "\ncreated new cluster with pattern: " + << pattern << "\n" + << " and lemma cube: " << lcube << "\n";); + + for (const lemma_ref &l : neighbours) { + SASSERT(cluster->can_contain(l)); + bool added = cluster->add_lemma(l, false); + CTRACE("cluster_stats", added, + tout << "Added neighbour lemma\n" << mk_and(l->get_cube()) << "\n";); + } + + // finally add the lemma and do subsumption check + cluster->add_lemma(lemma, true); + SASSERT(cluster->get_size() >= 1); +} + +void lemma_cluster_finder::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.cluster", m_st.watch.get_seconds()); +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_cluster.h b/src/muz/spacer/spacer_cluster.h new file mode 100644 index 000000000..fb5ab1d28 --- /dev/null +++ b/src/muz/spacer/spacer_cluster.h @@ -0,0 +1,176 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster.h + +Abstract: + + Discover and mark lemma clusters + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include +#include +#include +#include +#include +#include + +#define GAS_POB_COEFF 5 + +namespace spacer { +class lemma; +using lemma_ref = ref; + +/// Representation of a cluster of lemmas +/// +/// A cluster of lemmas is a collection of lemma instances. A cluster is +/// defined by a \p pattern that is a qff formula with free variables, and +/// contains lemmas that are instances of the pattern (i.e., obtained from the +/// pattern by substitution of constants for variables). That is, each lemma +/// in the cluster matches the pattern. +class lemma_cluster { + /// Lemma in a cluster + /// + /// A lemma and a substitution witnessing that lemma is an instance of a + /// pattern + class lemma_info { + // a lemma + lemma_ref m_lemma; + // a substitution such that for some pattern, \p m_lemma is an instance + // substitution is stored in std_order for quantifiers (i.e., reverse of + // expected) + substitution m_sub; + + public: + lemma_info(const lemma_ref &body, const substitution &sub) + : m_lemma(body), m_sub(sub) {} + + const lemma_ref &get_lemma() const { return m_lemma; } + const substitution &get_sub() const { return m_sub; } + }; + + public: + using lemma_info_vector = vector; + + private: + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // reference counter + unsigned m_ref_count; + // pattern defining the cluster + expr_ref m_pattern; + unsigned m_num_vars; + + // vector of lemmas in the cluster + lemma_info_vector m_lemma_vec; + + // shared matcher object to match lemmas against the pattern + sem_matcher m_matcher; + + // The number of times CSM has to be tried using this cluster + unsigned m_gas; + + /// Remove subsumed lemmas in the cluster. + /// + /// Returns list of removed lemmas in \p removed_lemmas + void rm_subsumed(lemma_info_vector &removed_lemmas); + + /// Checks whether \p e matches m_pattern. + /// + /// Returns true on success and sets \p sub to the corresponding + /// substitution + bool match(const expr_ref &e, substitution &sub); + + ast_manager &get_manager() const { return m; } + + public: + lemma_cluster(const expr_ref &pattern); + lemma_cluster(const lemma_cluster &other); + + const lemma_info_vector &get_lemmas() const { return m_lemma_vec; } + + void dec_gas() { + if (m_gas > 0) m_gas--; + } + + unsigned get_gas() const { return m_gas; } + unsigned get_pob_gas() const { return GAS_POB_COEFF * m_lemma_vec.size(); } + + /// Get a conjunction of all the lemmas in cluster + void get_conj_lemmas(expr_ref &e) const; + + /// Try to add \p lemma to cluster. Remove subsumed lemmas if \p subs_check + /// is true + /// + /// Returns false if lemma does not match the pattern or if it is already in + /// the cluster Repetition of lemmas is avoided by doing a linear scan over + /// the lemmas in the cluster. Adding a lemma can reduce the size of the + /// cluster due to subs_check + bool add_lemma(const lemma_ref &lemma, bool subsume = false); + + bool contains(const lemma_ref &lemma); + bool can_contain(const lemma_ref &lemma); + + /// Return the minimum level of lemmas in he cluster + unsigned get_min_lvl(); + + lemma_cluster::lemma_info *get_lemma_info(const lemma_ref &lemma); + unsigned get_size() const { return m_lemma_vec.size(); } + const expr_ref &get_pattern() const { return m_pattern; } + + void inc_ref() { ++m_ref_count; } + void dec_ref() { + --m_ref_count; + if (m_ref_count == 0) { dealloc(this); } + } +}; + +class lemma_cluster_finder { + struct stats { + unsigned max_group_size; + stopwatch watch; + stats() { reset(); } + void reset() { + max_group_size = 0; + watch.reset(); + } + }; + stats m_st; + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + /// Check whether \p cube and \p lcube differ only in interpreted constants + bool are_neighbours(const expr_ref &cube, const expr_ref &lcube); + + /// N-ary antiunify + /// + /// Returns whether there is a substitution with only interpreted consts + bool anti_unify_n_intrp(const expr_ref &cube, expr_ref_vector &fmls, + expr_ref &res); + + public: + lemma_cluster_finder(ast_manager &m); + + /// Add a new lemma \p lemma to a cluster + /// + /// Creates a new cluster for the lemma if necessary + void cluster(lemma_ref &lemma); + void collect_statistics(statistics &st) const; + void reset_statistics() { m_st.reset(); } +}; + +using lemma_info_vector = lemma_cluster::lemma_info_vector; +} // namespace spacer diff --git a/src/muz/spacer/spacer_cluster_util.cpp b/src/muz/spacer/spacer_cluster_util.cpp new file mode 100644 index 000000000..72d1d2269 --- /dev/null +++ b/src/muz/spacer/spacer_cluster_util.cpp @@ -0,0 +1,240 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster_util.cpp + +Abstract: + + Utility methods for clustering + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "ast/arith_decl_plugin.h" +#include "ast/ast.h" +#include "ast/ast_pp.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/rewriter.h" +#include "ast/rewriter/rewriter_def.h" +#include "ast/rewriter/th_rewriter.h" +#include "muz/spacer/spacer_util.h" + +namespace spacer { +/// Arithmetic term order +struct arith_add_less_proc { + const arith_util &m_arith; + + arith_add_less_proc(const arith_util &arith) : m_arith(arith) {} + + bool operator()(expr *e1, expr *e2) const { + if (e1 == e2) return false; + + ast_lt_proc ast_lt; + expr *k1 = nullptr, *t1 = nullptr, *k2 = nullptr, *t2 = nullptr; + + // k1*t1 < k2*t2 iff t1 < t2 or t1 == t2 && k1 < k2 + // k1 and k2 can be null + + if (!m_arith.is_mul(e1, k1, t1)) { t1 = e1; } + if (!m_arith.is_mul(e2, k2, t2)) { t2 = e2; } + + SASSERT(t1 && t2); + if (t1 != t2) return ast_lt(t1, t2); + + // here: t1 == t2 && k1 != k2 + SASSERT(k1 != k2); + + // check for null + if (!k1 || !k2) return !k1; + return ast_lt(k1, k2); + } +}; + +struct bool_and_less_proc { + ast_manager &m; + const arith_util &m_arith; + bool_and_less_proc(ast_manager &mgr, const arith_util &arith) + : m(mgr), m_arith(arith) {} + + bool operator()(expr *e1, expr *e2) const { + expr *a1 = nullptr, *a2 = nullptr; + bool is_not1, is_not2; + if (e1 == e2) return false; + + is_not1 = m.is_not(e1, a1); + a1 = is_not1 ? a1 : e1; + is_not2 = m.is_not(e2, a2); + a2 = is_not2 ? a2 : e2; + + return a1 == a2 ? is_not1 < is_not2 : arith_lt(a1, a2); + } + + bool arith_lt(expr *e1, expr *e2) const { + ast_lt_proc ast_lt; + expr *t1, *k1, *t2, *k2; + + if (e1 == e2) return false; + + if (e1->get_kind() != e2->get_kind()) return e1->get_kind() < e2->get_kind(); + if (!is_app(e1)) return ast_lt(e1, e2); + + app *a1 = to_app(e1), *a2 = to_app(e2); + + if (a1->get_family_id() != a2->get_family_id()) + return a1->get_family_id() < a2->get_family_id(); + if (a1->get_decl_kind() != a2->get_decl_kind()) + return a1->get_decl_kind() < a2->get_decl_kind(); + + if (!(m_arith.is_le(e1, t1, k1) || m_arith.is_lt(e1, t1, k1) || + m_arith.is_ge(e1, t1, k1) || m_arith.is_gt(e1, t1, k1))) { + t1 = e1; + k1 = nullptr; + } + if (!(m_arith.is_le(e2, t2, k2) || m_arith.is_lt(e2, t2, k2) || + m_arith.is_ge(e2, t2, k2) || m_arith.is_gt(e2, t2, k2))) { + t2 = e2; + k2 = nullptr; + } + + if (!k1 || !k2) { return k1 == k2 ? ast_lt(t1, t2) : k1 < k2; } + + if (t1 == t2) return ast_lt(k1, k2); + + if (t1->get_kind() != t2->get_kind()) + return t1->get_kind() < t2->get_kind(); + + if (!is_app(t1)) return ast_lt(t1, t2); + + unsigned d1 = to_app(t1)->get_depth(); + unsigned d2 = to_app(t2)->get_depth(); + if (d1 != d2) return d1 < d2; + + // AG: order by the leading uninterpreted constant + expr *u1 = nullptr, *u2 = nullptr; + + u1 = get_first_uc(t1); + u2 = get_first_uc(t2); + if (!u1 || !u2) { return u1 == u2 ? ast_lt(t1, t2) : u1 < u2; } + return u1 == u2 ? ast_lt(t1, t2) : ast_lt(u1, u2); + } + + /// Returns first in left-most traversal uninterpreted constant of \p e + /// + /// Returns null when no uninterpreted constant is found. + /// Recursive, assumes that expression is shallow and recursion is bounded. + expr *get_first_uc(expr *e) const { + expr *t, *k; + if (is_uninterp_const(e)) + return e; + else if (m_arith.is_add(e)) { + if (to_app(e)->get_num_args() == 0) return nullptr; + expr *a1 = to_app(e)->get_arg(0); + // HG: for 3 + a, returns nullptr + return get_first_uc(a1); + } else if (m_arith.is_mul(e, k, t)) { + return get_first_uc(t); + } + return nullptr; + } +}; + +// Rewriter for normalize_order() +struct term_ordered_rpp : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + arith_add_less_proc m_add_less; + bool_and_less_proc m_and_less; + + term_ordered_rpp(ast_manager &man) + : m(man), m_arith(m), m_add_less(m_arith), m_and_less(m, m_arith) {} + + bool is_add(func_decl const *n) const { + return is_decl_of(n, m_arith.get_family_id(), OP_ADD); + } + + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + br_status st = BR_FAILED; + + if (is_add(f)) { + ptr_buffer kids; + kids.append(num, args); + std::stable_sort(kids.data(), kids.data() + kids.size(), + m_add_less); + result = m_arith.mk_add(num, kids.data()); + return BR_DONE; + } + + if (m.is_and(f)) { + ptr_buffer kids; + kids.append(num, args); + std::stable_sort(kids.data(), kids.data() + kids.size(), + m_and_less); + result = m.mk_and(num, kids.data()); + return BR_DONE; + } + return st; + } +}; + +// Normalize an arithmetic expression using term order +void normalize_order(expr *e, expr_ref &out) { + params_ref params; + // -- arith_rewriter params + params.set_bool("sort_sums", true); + // params.set_bool("gcd_rounding", true); + // params.set_bool("arith_lhs", true); + // -- poly_rewriter params + // params.set_bool("som", true); + // params.set_bool("flat", true); + + // apply theory rewriter + th_rewriter rw1(out.m(), params); + rw1(e, out); + + STRACE("spacer_normalize_order'", + tout << "OUT Before:" << mk_pp(out, out.m()) << "\n";); + // apply term ordering + term_ordered_rpp t_ordered(out.m()); + rewriter_tpl rw2(out.m(), false, t_ordered); + rw2(out.get(), out); + STRACE("spacer_normalize_order'", + tout << "OUT After :" << mk_pp(out, out.m()) << "\n";); +} + +/// Multiply an expression \p fml by a rational \p num +/// +/// \p fml should be of sort Int, Real, or BitVec +/// multiplication is simplifying +void mul_by_rat(expr_ref &fml, rational num) { + if (num.is_one()) return; + + ast_manager &m = fml.get_manager(); + arith_util m_arith(m); + bv_util m_bv(m); + expr_ref e(m); + SASSERT(m_arith.is_int_real(fml) || m_bv.is_bv(fml)); + if (m_arith.is_int_real(fml)) { + e = m_arith.mk_mul(m_arith.mk_numeral(num, m_arith.is_int(fml)), fml); + } else if (m_bv.is_bv(fml)) { + unsigned sz = m_bv.get_bv_size(fml); + e = m_bv.mk_bv_mul(m_bv.mk_numeral(num, sz), fml); + } + + // use theory rewriter to simplify + params_ref params; + params.set_bool("som", true); + params.set_bool("flat", true); + th_rewriter rw(m, params); + rw(e, fml); +} +} // namespace spacer +template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_concretize.cpp b/src/muz/spacer/spacer_concretize.cpp new file mode 100644 index 000000000..809e9a971 --- /dev/null +++ b/src/muz/spacer/spacer_concretize.cpp @@ -0,0 +1,208 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_concretize.cpp + +Abstract: + + Concretize a pob + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include "spacer_concretize.h" + +namespace pattern_var_marker_ns { +struct proc { + arith_util &m_arith; + expr_fast_mark2 &m_marks; + proc(arith_util &arith, expr_fast_mark2 &marks) + : m_arith(arith), m_marks(marks) {} + void operator()(var *n) const {} + void operator()(quantifier *q) const {} + void operator()(app const *n) const { + expr *e1, *e2; + if (m_arith.is_mul(n, e1, e2)) { + if (is_var(e1) && !is_var(e2)) + m_marks.mark(e2); + else if (is_var(e2) && !is_var(e1)) + m_marks.mark(e1); + } + } +}; +}; // namespace pattern_var_marker_ns +namespace spacer { +void pob_concretizer::mark_pattern_vars() { + pattern_var_marker_ns::proc proc(m_arith, m_var_marks); + quick_for_each_expr(proc, const_cast(m_pattern)); +} + +bool pob_concretizer::push_out(expr_ref_vector &out, const expr_ref &e) { + // using m_var_marks to mark both variables and expressions sent to out + // the two sets are distinct so we can reuse the same marks + if (!m_var_marks.is_marked(e)) { + m_var_marks.mark(e); + out.push_back(e); + return true; + } + return false; +} + +bool pob_concretizer::apply(const expr_ref_vector &cube, expr_ref_vector &out) { + // mark variables that are being split out + mark_pattern_vars(); + + for (auto *lit : cube) { + if (!apply_lit(lit, out)) { + out.reset(); + m_var_marks.reset(); + return false; + } + } + + m_var_marks.reset(); + return true; +} + +bool pob_concretizer::is_split_var(expr *e, expr *&var, bool &pos) { + expr *e1, *e2; + rational n; + + if (m_var_marks.is_marked(e)) { + var = e; + pos = true; + return true; + } else if (m_arith.is_mul(e, e1, e2) && m_arith.is_numeral(e1, n) && + m_var_marks.is_marked(e2)) { + var = e2; + pos = !n.is_neg(); + return true; + } + + return false; +} + +void pob_concretizer::split_lit_le_lt(expr *_lit, expr_ref_vector &out) { + expr *e1, *e2; + + expr *lit = _lit; + m.is_not(_lit, lit); + VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) || + m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)); + + ptr_buffer kids; + expr *var; + bool pos; + expr_ref val(m); + for (auto *arg : *to_app(e1)) { + if (is_split_var(arg, var, pos)) { + val = m_model->operator()(var); + + // reuse val to keep the new literal + val = pos ? m_arith.mk_le(var, val) : m_arith.mk_ge(var, val); + push_out(out, val); + } else { + kids.push_back(arg); + } + } + + if (kids.empty()) return; + + // -- nothing was changed in the literal, move it out as is + if (kids.size() == to_app(e1)->get_num_args()) { + push_out(out, {_lit, m}); + return; + } + + // create new leftover literal using remaining arguments + expr_ref lhs(m); + if (kids.size() == 1) { + lhs = kids.get(0); + } else + lhs = m_arith.mk_add(kids.size(), kids.data()); + + expr_ref rhs = m_model->operator()(lhs); + expr_ref new_lit(m_arith.mk_le(lhs, rhs), m); + push_out(out, new_lit); +} + +void pob_concretizer::split_lit_ge_gt(expr *_lit, expr_ref_vector &out) { + expr *e1, *e2; + + expr *lit = _lit; + m.is_not(_lit, lit); + VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) || + m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)); + + ptr_buffer kids; + expr *var; + bool pos; + expr_ref val(m); + for (auto *arg : *to_app(e1)) { + if (is_split_var(arg, var, pos)) { + val = m_model->operator()(var); + + // reuse val to keep the new literal + val = pos ? m_arith.mk_ge(var, val) : m_arith.mk_le(var, val); + push_out(out, val); + } else { + kids.push_back(arg); + } + } + + if (kids.empty()) return; + + // -- nothing was changed in the literal, move it out as is + if (kids.size() == to_app(e1)->get_num_args()) { + push_out(out, {_lit, m}); + return; + } + + // create new leftover literal using remaining arguments + expr_ref lhs(m); + if (kids.size() == 1) { + lhs = kids.get(0); + } else + lhs = m_arith.mk_add(kids.size(), kids.data()); + + expr_ref rhs = m_model->operator()(lhs); + expr_ref new_lit(m_arith.mk_ge(lhs, rhs), m); + push_out(out, new_lit); +} + +bool pob_concretizer::apply_lit(expr *_lit, expr_ref_vector &out) { + expr *lit = _lit; + bool is_neg = m.is_not(_lit, lit); + + // split literals of the form a1*x1 + ... + an*xn ~ c, where c is a + // constant, ~ is <, <=, >, or >=, and the top level operator of LHS is + + expr *e1, *e2; + if ((m_arith.is_lt(lit, e1, e2) || m_arith.is_le(lit, e1, e2)) && + m_arith.is_add(e1)) { + SASSERT(m_arith.is_numeral(e2)); + if (!is_neg) { + split_lit_le_lt(_lit, out); + } else { + split_lit_ge_gt(_lit, out); + } + } else if ((m_arith.is_gt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)) && + m_arith.is_add(e1)) { + SASSERT(m_arith.is_numeral(e2)); + if (!is_neg) { + split_lit_ge_gt(_lit, out); + } else { + split_lit_le_lt(_lit, out); + } + } else { + out.push_back(_lit); + } + return true; +} +} // namespace spacer + diff --git a/src/muz/spacer/spacer_concretize.h b/src/muz/spacer/spacer_concretize.h new file mode 100644 index 000000000..6cefb58db --- /dev/null +++ b/src/muz/spacer/spacer_concretize.h @@ -0,0 +1,77 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_concretize.h + +Abstract: + + Concretize a pob + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "ast/ast.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_util.h" +#include "tactic/core/ctx_simplify_tactic.h" +#include "util/obj_ref_hashtable.h" +#include "util/rational.h" + +namespace spacer { + +class pob_concretizer { + ast_manager &m; + arith_util m_arith; + + model_ref &m_model; + + const expr *m_pattern; + + expr_fast_mark2 m_var_marks; + + // Marks all constants to be split in the pattern + void mark_pattern_vars(); + + // Adds a literal to \p out (unless it is already there) + bool push_out(expr_ref_vector &out, const expr_ref &e); + // Determines whether \p e is a*var, for some variable in \p m_var_marks + // Sets \p pos to sign(a) + bool is_split_var(expr *e, expr *&var, bool &pos); + // Splits a < or <= literal using the model + void split_lit_le_lt(expr *lit, expr_ref_vector &out); + // See split_lit_le_lt + void split_lit_ge_gt(expr *lit, expr_ref_vector &out); + + public: + pob_concretizer(ast_manager &_m, model_ref &model, const expr *pattern) + : m(_m), m_arith(m), m_model(model), m_pattern(pattern) {} + + /// Concretize \p cube into conjunction of simpler literals + /// + /// Returns true on success and adds new literals to out + /// ensures: mk_and(out) ==> cube + bool apply(expr *cube, expr_ref_vector &out) { + expr_ref_vector flat(m); + flatten_and(cube, flat); + return apply(flat, out); + } + + /// Concretizes a vector of literals + bool apply(const expr_ref_vector &cube, expr_ref_vector &out); + + /// Concretizes a single literal + /// + /// Returns true on success, new literals are added to \p out + bool apply_lit(expr *lit, expr_ref_vector &out); +}; + +} // namespace spacer diff --git a/src/muz/spacer/spacer_conjecture.cpp b/src/muz/spacer/spacer_conjecture.cpp new file mode 100644 index 000000000..e95f0c7b3 --- /dev/null +++ b/src/muz/spacer/spacer_conjecture.cpp @@ -0,0 +1,98 @@ +/** + Copyright (c) 2019 Microsoft Corporation and Arie Gurfinkel + + Module Name: + + spacer_conjecture.cpp + + Abstract: + + Methods to implement conjecture rule in gspacer + + Author: + + Arie Gurfinkel + Hari Govind + + + Notes: + +--*/ + +#include "ast/for_each_expr.h" + +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_util.h" + +namespace spacer { +/// Returns true if \p lit is LA/BV inequality with a single free variable +bool is_mono_var_lit(expr *lit, ast_manager &m) { + expr *e; + bv_util bv(m); + arith_util a_util(m); + if (m.is_not(lit, e)) return is_mono_var_lit(e, m); + if (a_util.is_arith_expr(lit) || bv.is_bv_ule(lit) || + bv.is_bv_sle(lit)) { + return get_num_vars(lit) == 1 && !has_nonlinear_var_mul(lit, m); + } + return false; +} + +/// Returns true if \p pattern contains a single mono-var literal +/// +/// That is, \p pattern contains a single suitable literal. +/// The literal is returned in \p res +bool find_unique_mono_var_lit(const expr_ref &pattern, expr_ref &res) { + if (get_num_vars(pattern) != 1) return false; + ast_manager &m = res.m(); + + // if the pattern has multiple literals, check whether exactly one of them + // is leq + expr_ref_vector conj(m); + conj.push_back(pattern); + flatten_and(conj); + unsigned count = 0; + for (auto *lit : conj) { + if (is_mono_var_lit(lit, m)) { + if (count) return false; + res = lit; + count++; + } + } + SASSERT(count <= 1); + return count == 1; +} + +/// Filter out a given literal \p lit from a list of literals +/// +/// Returns true if at least one literal was filtered out +/// \p out contains all the remaining (not filtered) literals +/// \p out holds the result. Returns true if any literal has been dropped +bool filter_out_lit(const expr_ref_vector &vec, const expr_ref &lit, expr_ref_vector &out) { + ast_manager &m = vec.get_manager(); + bool dirty = false, pos = false; + sem_matcher matcher(m); + substitution sub(m); + + out.reset(); + unsigned lit_num_vars = get_num_vars(lit.get()); + SASSERT(!(m.is_not(lit) && m.is_eq(to_app(lit)->get_arg(0)))); + for (auto &c : vec) { + sub.reset(); + sub.reserve(1, lit_num_vars); + matcher.reset(); + + if (matcher(lit, c, sub, pos) && pos) { + if (is_numeric_sub(sub)) { + dirty = true; + continue; + } + } + out.push_back(c); + } + + CTRACE("global", dirty, + tout << "Filtered " << lit << " from " << vec << "\n got " << out << "\n";); + return dirty; +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 42b366d94..4c5c13e34 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -20,6 +20,7 @@ Notes: --*/ +// clang-format off #include #include @@ -51,30 +52,36 @@ Notes: #include "muz/transforms/dl_mk_rule_inliner.h" #include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_sat_answer.h" +#include "muz/spacer/spacer_concretize.h" +#include "muz/spacer/spacer_global_generalizer.h" #define WEAKNESS_MAX 65535 namespace spacer { /// pob -- proof obligation -pob::pob (pob* parent, pred_transformer& pt, - unsigned level, unsigned depth, bool add_to_parent): - m_ref_count (0), - m_parent (parent), m_pt (pt), - m_post (m_pt.get_ast_manager ()), - m_binding(m_pt.get_ast_manager()), - m_new_post (m_pt.get_ast_manager ()), - m_level (level), m_depth (depth), - m_open (true), m_use_farkas (true), m_in_queue(false), - m_weakness(0), m_blocked_lvl(0) { +pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth, + bool add_to_parent) + : m_ref_count(0), m_parent(parent), m_pt(pt), + m_post(m_pt.get_ast_manager()), m_binding(m_pt.get_ast_manager()), + m_new_post(m_pt.get_ast_manager()), m_level(level), m_depth(depth), + m_desired_level(0), m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false), + m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false), + m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0), + m_concretize_pat(m_pt.get_ast_manager()), + m_gas(0) { if (add_to_parent && m_parent) { m_parent->add_child(*this); } + if (m_parent) { + m_is_conjecture = m_parent->is_conjecture(); + // m_is_subsume = m_parent->is_subsume(); + m_gas = m_parent->get_gas(); + } } void pob::set_post(expr* post) { - app_ref_vector empty_binding(get_ast_manager()); - set_post(post, empty_binding); + set_post(post, {get_ast_manager()}); } void pob::set_post(expr* post, app_ref_vector const &binding) { @@ -91,6 +98,11 @@ void pob::inherit(pob const &p) { SASSERT(!is_in_queue()); SASSERT(m_parent == p.m_parent); SASSERT(&m_pt == &p.m_pt); + + // -- HACK: normalize second time because th_rewriter is not idempotent + if (m_post != p.m_post) { + normalize(m_post, m_post, false, false); + } SASSERT(m_post == p.m_post); SASSERT(!m_new_post); @@ -99,11 +111,21 @@ void pob::inherit(pob const &p) { m_level = p.m_level; m_depth = p.m_depth; + m_desired_level = std::max(m_desired_level, p.m_desired_level); m_open = p.m_open; m_use_farkas = p.m_use_farkas; + + m_is_conjecture = p.m_is_conjecture; + m_enable_local_gen = p.m_enable_local_gen; + m_enable_concretize = p.m_enable_concretize; + m_is_subsume = p.m_is_subsume; + m_enable_expand_bnd_gen = p.m_enable_expand_bnd_gen; + m_weakness = p.m_weakness; m_derivation = nullptr; + + m_gas = p.m_gas; } void pob::close () { @@ -756,6 +778,8 @@ void pred_transformer::collect_statistics(statistics& st) const m_must_reachable_watch.get_seconds ()); st.update("time.spacer.ctp", m_ctp_watch.get_seconds()); st.update("time.spacer.mbp", m_mbp_watch.get_seconds()); + // -- Max cluster size can decrease during run + st.update("SPACER max cluster size", m_cluster_db.get_max_cluster_size()); } void pred_transformer::reset_statistics() @@ -1193,6 +1217,7 @@ expr_ref pred_transformer::get_origin_summary (model &mdl, for (auto* s : summary) { if (!is_quantifier(s) && !mdl.is_true(s)) { TRACE("spacer", tout << "Summary not true in the model: " << mk_pp(s, m) << "\n";); + return expr_ref(m); } } @@ -1258,12 +1283,11 @@ void pred_transformer::get_pred_bg_invs(expr_ref_vector& out) { /// \brief Returns true if the obligation is already blocked by current lemmas -bool pred_transformer::is_blocked (pob &n, unsigned &uses_level) -{ +bool pred_transformer::is_blocked(pob &n, unsigned &uses_level, model_ref *model) { ensure_level (n.level ()); prop_solver::scoped_level _sl (*m_solver, n.level ()); m_solver->set_core (nullptr); - m_solver->set_model (nullptr); + m_solver->set_model(model); expr_ref_vector post(m), _aux(m); post.push_back (n.post ()); @@ -1335,7 +1359,7 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, model_ref* model, unsigned& uses_level, bool& is_concrete, datalog::rule const*& r, bool_vector& reach_pred_used, - unsigned& num_reuse_reach) + unsigned& num_reuse_reach, bool use_iuc) { TRACE("spacer", tout << "is-reachable: " << head()->get_name() << " level: " @@ -1349,7 +1373,8 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, // prepare the solver prop_solver::scoped_level _sl(*m_solver, n.level()); - prop_solver::scoped_subset_core _sc (*m_solver, !n.use_farkas_generalizer ()); + prop_solver::scoped_subset_core _sc( + *m_solver, !(use_iuc && n.use_farkas_generalizer())); prop_solver::scoped_weakness _sw(*m_solver, 0, ctx.weak_abs() ? n.weakness() : UINT_MAX); m_solver->set_core(core); @@ -1406,9 +1431,10 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach); TRACE("spacer", tout << "reachable is_sat: " << is_sat << " " - << r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n"; - ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout) << "\n"; - ); + << r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n";); + CTRACE("spacer", r, + ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout); + tout << "\n";); TRACE("spacer_sat", tout << "model is:\n" << **model << "\n";); } @@ -2272,7 +2298,8 @@ context::context(fp_params const& params, ast_manager& m) : m_last_result(l_undef), m_inductive_lvl(0), m_expanded_lvl(0), - m_json_marshaller(this), + m_global_gen(nullptr), + m_expand_bnd_gen(nullptr), m_trace_stream(nullptr) { params_ref p; @@ -2290,6 +2317,7 @@ context::context(fp_params const& params, ast_manager& m) : m_pool1 = alloc(solver_pool, pool1_base.get(), max_num_contexts); m_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts); + m_lmma_cluster = alloc(lemma_cluster_finder, m); updt_params(); if (m_params.spacer_trace_file().is_non_empty_string()) { @@ -2302,6 +2330,7 @@ context::context(fp_params const& params, ast_manager& m) : context::~context() { reset_lemma_generalizers(); + dealloc(m_lmma_cluster); reset(); if (m_trace_stream) { @@ -2347,7 +2376,13 @@ void context::updt_params() { m_restart_initial_threshold = m_params.spacer_restart_initial_threshold(); m_pdr_bfs = m_params.spacer_gpdr_bfs(); m_use_bg_invs = m_params.spacer_use_bg_invs(); + m_global = m_params.spacer_global(); + m_expand_bnd = m_params.spacer_expand_bnd(); + m_gg_conjecture = m_params.spacer_gg_conjecture(); + m_gg_subsume = m_params.spacer_gg_subsume(); + m_gg_concretize = m_params.spacer_gg_concretize(); + m_use_iuc = m_params.spacer_use_iuc(); if (m_use_gpdr) { // set options to be compatible with GPDR m_weak_abs = false; @@ -2665,7 +2700,8 @@ void context::init_lemma_generalizers() //m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this)); if (m_use_ind_gen) { - m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); + // m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); + m_lemma_generalizers.push_back(alloc_lemma_inductive_generalizer(*this)); } // after the lemma is minimized (maybe should also do before) @@ -2678,6 +2714,15 @@ void context::init_lemma_generalizers() m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this)); } + if (m_global) { + m_global_gen = alloc(lemma_global_generalizer, *this); + m_lemma_generalizers.push_back(m_global_gen); + } + + if (m_expand_bnd) { + m_expand_bnd_gen = alloc(lemma_expand_bnd_generalizer, *this); + m_lemma_generalizers.push_back(m_expand_bnd_gen); + } if (m_validate_lemmas) { m_lemma_generalizers.push_back(alloc(lemma_sanity_checker, *this)); } @@ -3020,9 +3065,7 @@ lbool context::solve_core (unsigned from_lvl) if (check_reachability()) { return l_true; } if (lvl > 0 && m_use_propagate) - if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { dump_json(); return l_false; } - - dump_json(); + if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { return l_false; } if (is_inductive()){ return l_false; @@ -3070,6 +3113,8 @@ void context::log_expand_pob(pob &n) { if (n.parent()) pob_id = std::to_string(n.parent()->post()->get_id()); *m_trace_stream << "** expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? " CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) << " exprID: " << n.post()->get_id() << " pobID: " << pob_id << "\n" @@ -3077,13 +3122,18 @@ void context::log_expand_pob(pob &n) { } TRACE("spacer", tout << "expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? " CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) - << " fvsz: " << n.get_free_vars_size() << "\n" + << " fvsz: " << n.get_free_vars_size() + << " gas: " << n.get_gas() << "\n" << mk_pp(n.post(), m) << "\n";); STRACE("spacer_progress", tout << "** expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? " CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n" << mk_epp(n.post(), m) << "\n\n";); @@ -3151,13 +3201,21 @@ bool context::check_reachability () node = last_reachable; last_reachable = nullptr; if (m_pob_queue.is_root(*node)) { return true; } - if (is_reachable (*node->parent())) { - last_reachable = node->parent (); + + // do not check the parent if its may pob status is different + if (node->parent()->is_may_pob() != node->is_may_pob()) + { + last_reachable = nullptr; + break; + } + + if (is_reachable(*node->parent())) { + last_reachable = node->parent(); SASSERT(last_reachable->is_closed()); - last_reachable->close (); + last_reachable->close(); } else if (!node->parent()->is_closed()) { /* bump node->parent */ - node->parent ()->bump_weakness(); + node->parent()->bump_weakness(); } } @@ -3202,20 +3260,37 @@ bool context::check_reachability () case l_true: SASSERT(m_pob_queue.size() == old_sz); SASSERT(new_pobs.empty()); + node->close(); last_reachable = node; - last_reachable->close (); - if (m_pob_queue.is_root(*node)) {return true;} + if (m_pob_queue.is_root(*node)) { return true; } break; case l_false: SASSERT(m_pob_queue.size() == old_sz); + // re-queue all pobs introduced by global gen and any pobs that can be blocked at a higher level for (auto pob : new_pobs) { - if (is_requeue(*pob)) {m_pob_queue.push(*pob);} + TRACE("gg", tout << "pob: is_may_pob " << pob->is_may_pob() + << " with post:\n" + << mk_pp(pob->post(), m) + << "\n";); + //if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) { + if (is_requeue(*pob)) { + TRACE("gg", + tout << "Adding back blocked pob at level " + << pob->level() + << " and depth " << pob->depth() << "\n"); + m_pob_queue.push(*pob); + } } if (m_pob_queue.is_root(*node)) {return false;} break; case l_undef: SASSERT(m_pob_queue.size() == old_sz); + // collapse may pobs if the reachability of one of them cannot + // be estimated + if ((node->is_may_pob()) && new_pobs.size() == 0) { + close_all_may_parents(node); + } for (auto pob : new_pobs) {m_pob_queue.push(*pob);} break; } @@ -3228,7 +3303,10 @@ bool context::check_reachability () /// returns true if the given pob can be re-scheduled bool context::is_requeue(pob &n) { - if (!m_push_pob) {return false;} + // if have not reached desired level, then requeue + if (n.level() <= n.desired_level()) { return true; } + if (!m_push_pob) { return false; } + unsigned max_depth = m_push_pob_max_depth; return (n.level() >= m_pob_queue.max_level() || m_pob_queue.max_level() - n.level() <= max_depth); @@ -3270,9 +3348,9 @@ bool context::is_reachable(pob &n) unsigned saved = n.level (); // TBD: don't expose private field n.m_level = infty_level (); - lbool res = n.pt().is_reachable(n, nullptr, &mdl, - uses_level, is_concrete, r, - reach_pred_used, num_reuse_reach); + lbool res = + n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach, m_use_iuc); n.m_level = saved; if (res != l_true || !is_concrete) { @@ -3326,16 +3404,6 @@ bool context::is_reachable(pob &n) return next ? is_reachable(*next) : true; } -void context::dump_json() -{ - if (m_params.spacer_print_json().is_non_empty_string()) { - std::ofstream of; - of.open(m_params.spacer_print_json().bare_str()); - m_json_marshaller.marshal(of); - of.close(); - } -} - void context::predecessor_eh() { for (unsigned i = 0; i < m_callbacks.size(); i++) { @@ -3446,14 +3514,15 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) log_expand_pob(n); stopwatch watch; - IF_VERBOSE (1, verbose_stream () << "expand: " << n.pt ().head ()->get_name () - << " (" << n.level () << ", " + IF_VERBOSE(1, verbose_stream() + << "expand: " << n.pt().head()->get_name() << " (" + << n.level() << ", " << (n.depth () - m_pob_queue.min_depth ()) << ") " << (n.use_farkas_generalizer () ? "FAR " : "SUB ") - << " w(" << n.weakness() << ") " - << n.post ()->get_id (); - verbose_stream().flush (); - watch.start ();); + << (n.is_conjecture() ? "CONJ " : "") + << (n.is_subsume() ? " SUBS" : "") << " w(" + << n.weakness() << ") " << n.post()->get_id(); + verbose_stream().flush(); watch.start();); // used in case n is unreachable unsigned uses_level = infty_level (); @@ -3468,25 +3537,59 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) unsigned num_reuse_reach = 0; - if (m_push_pob && n.pt().is_blocked(n, uses_level)) { + if (!n.is_may_pob() && m_push_pob && n.pt().is_blocked(n, uses_level)) { // if (!m_pob_queue.is_root (n)) n.close (); IF_VERBOSE (1, verbose_stream () << " K " << std::fixed << std::setprecision(2) - << watch.get_seconds () << "\n";); - n.inc_level(); - out.push_back(&n); - return l_false; - } + << watch.get_seconds () << "\n";); + n.inc_level(); + out.push_back(&n); + return l_false; + } - if (/* XXX noop */ n.pt().is_qblocked(n)) { - STRACE("spacer_progress", - tout << "This pob can be blocked by instantiation\n";); - } + if (/* XXX noop */ n.pt().is_qblocked(n)) { + STRACE("spacer_progress", + tout << "This pob can be blocked by instantiation\n";); + } + if ((n.is_may_pob()) && n.get_gas() == 0) { + TRACE("global", tout << "Cant prove may pob. Collapsing " + << mk_pp(n.post(), m) << "\n";); + m_stats.m_num_pob_ofg++; + return l_undef; + } + // Decide whether to concretize pob + // get a model that satisfies the pob and the current set of lemmas + // TODO: if push_pob is enabled, avoid calling is_blocked twice + if (m_gg_concretize && n.is_concretize_enabled() && + !n.pt().is_blocked(n, uses_level, &model)) { + TRACE("global", + tout << "Concretizing: " << mk_pp(n.post(), m) << "\n" + << "\t" << n.get_gas() << " attempts left\n";); + + SASSERT(m_global_gen); + if (pob *new_pob = m_global_gen->mk_concretize_pob(n, model)) { + m_stats.m_num_concretize++; + out.push_back(new_pob); + out.push_back(&n); + IF_VERBOSE(1, verbose_stream() + << " C " << std::fixed << std::setprecision(2) + << watch.get_seconds() << "\n";); + unsigned gas = n.get_gas(); + SASSERT(gas > 0); + // dec gas for orig pob to limit number of concretizations + new_pob->set_gas(gas--); + n.set_gas(gas); + return l_undef; + } + } + + model = nullptr; predecessor_eh(); - lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r, - reach_pred_used, num_reuse_reach); + lbool res = + n.pt().is_reachable(n, &cube, &model, uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach, m_use_iuc); if (model) model->set_model_completion(false); if (res == l_undef && model) res = handle_unknown(n, r, *model); @@ -3534,7 +3637,18 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) out.push_back (next); } } + if(n.is_subsume()) + m_stats.m_num_subsume_pob_reachable++; + if(n.is_conjecture()) + m_stats.m_num_conj_failed++; + CTRACE("global", n.is_conjecture(), + tout << "Failed to block conjecture " + << n.post()->get_id() << "\n";); + + CTRACE("global", n.is_subsume(), + tout << "Failed to block subsume generalization " + << mk_pp(n.post(), m) << "\n";); IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ") << std::fixed << std::setprecision(2) @@ -3565,7 +3679,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) throw unknown_exception(); } case l_false: { - // n is unreachable, create new summary facts + // n is unreachable, create a new lemma timeit _timer (is_trace_enabled("spacer_timeit"), "spacer::expand_pob::false", verbose_stream ()); @@ -3573,40 +3687,68 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) // -- only update expanded level when new lemmas are generated at it. if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); } - TRACE("spacer", tout << "cube:\n"; - for (unsigned j = 0; j < cube.size(); ++j) - tout << mk_pp(cube[j].get(), m) << "\n";); + TRACE("spacer", tout << "cube:\n" << cube << "\n";); + if(n.is_conjecture()) m_stats.m_num_conj_success++; + if(n.is_subsume()) m_stats.m_num_subsume_pob_blckd++; pob_ref nref(&n); + // -- create lemma from a pob and last unsat core - lemma_ref lemma = alloc(class lemma, pob_ref(&n), cube, uses_level); + lemma_ref lemma_pob; + if (n.is_local_gen_enabled()) { + lemma_pob = alloc(class lemma, nref, cube, uses_level); + // -- run all lemma generalizers + for (unsigned i = 0; + // -- only generalize if lemma was constructed using farkas + n.use_farkas_generalizer() && !lemma_pob->is_false() && + i < m_lemma_generalizers.size(); + ++i) { + checkpoint (); + (*m_lemma_generalizers[i])(lemma_pob); + } + } else if (m_global_gen || m_expand_bnd_gen) { + m_stats.m_non_local_gen++; - // -- run all lemma generalizers - for (unsigned i = 0; - // -- only generalize if lemma was constructed using farkas - n.use_farkas_generalizer () && !lemma->is_false() && - i < m_lemma_generalizers.size(); ++i) { - checkpoint (); - (*m_lemma_generalizers[i])(lemma); + expr_ref_vector pob_cube(m); + n.get_post_simplified(pob_cube); + + lemma_pob = alloc(class lemma, nref, pob_cube, n.level()); + TRACE("global", tout << "Disabled local gen on pob (id: " + << n.post()->get_id() << ")\n" + << mk_pp(n.post(), m) << "\n" + << "Lemma:\n" + << mk_and(lemma_pob->get_cube()) << "\n";); + if (m_global_gen) (*m_global_gen)(lemma_pob); + if (m_expand_bnd_gen) (*m_expand_bnd_gen)(lemma_pob); + } else { + lemma_pob = alloc(class lemma, nref, cube, uses_level); } - DEBUG_CODE( - lemma_sanity_checker sanity_checker(*this); - sanity_checker(lemma); - ); + CTRACE("global", n.is_conjecture() || n.is_subsume(), + tout << "Blocked " + << (n.is_conjecture() ? "conjecture " : "subsume ") << n.post()->get_id() + << " at level " << n.level() + << " using lemma\n" << mk_pp(lemma_pob->get_expr(), m) << "\n";); - TRACE("spacer", tout << "invariant state: " - << (is_infty_level(lemma->level())?"(inductive)":"") - << mk_pp(lemma->get_expr(), m) << "\n";); + DEBUG_CODE(lemma_sanity_checker sanity_checker(*this); + sanity_checker(lemma_pob);); - bool v = n.pt().add_lemma (lemma.get()); - if (v) { m_stats.m_num_lemmas++; } + TRACE("spacer", + tout << "invariant state: " + << (is_infty_level(lemma_pob->level()) ? "(inductive)" : "") + << mk_pp(lemma_pob->get_expr(), m) << "\n";); + + bool is_new = n.pt().add_lemma(lemma_pob.get()); + if (is_new) { + if (m_global) m_lmma_cluster->cluster(lemma_pob); + m_stats.m_num_lemmas++; + } // Optionally update the node to be the negation of the lemma - if (v && m_use_lemma_as_pob) { + if (is_new && m_use_lemma_as_pob) { expr_ref c(m); - c = mk_and(lemma->get_cube()); + c = mk_and(lemma_pob->get_cube()); // check that the post condition is different if (c != n.post()) { pob *f = n.pt().find_pob(n.parent(), c); @@ -3622,6 +3764,28 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) } } + if (m_global_gen) { + // if global gen is enabled, post-process the pob to create new subsume or conjecture pob + if (pob* new_pob = m_global_gen->mk_subsume_pob(n)) { + new_pob->set_gas(n.get_gas() - 1); + n.set_gas(n.get_gas() - 1); + out.push_back(new_pob); + m_stats.m_num_subsume_pobs++; + + TRACE("global_verbose", + tout << "New subsume pob\n" << mk_pp(new_pob->post(), m) << "\n" + << "gas:" << new_pob->get_gas() << "\n";); + } else if (pob* new_pob = m_gg_conjecture ? m_global_gen->mk_conjecture_pob(n) : nullptr) { + new_pob->set_gas(n.get_gas() - 1); + n.set_gas(n.get_gas() - 1); + out.push_back(new_pob); + m_stats.m_num_conj++; + + TRACE("global", + tout << "New conjecture pob\n" << mk_pp(new_pob->post(), m) << "\n";); + } + } + // schedule the node to be placed back in the queue n.inc_level(); out.push_back(&n); @@ -3636,7 +3800,24 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) return l_false; } case l_undef: - // something went wrong + // if the pob is a may pob, handle specially + if (n.is_may_pob()) { + // do not create children, but bump weakness + // bail out if this does not help + // AG: do not know why this is a good strategy + if (n.weakness() < 10) { + SASSERT(out.empty()); + n.bump_weakness(); + return expand_pob(n, out); + } + n.close(); + m_stats.m_expand_pob_undef++; + IF_VERBOSE(1, verbose_stream() << " UNDEF " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + return l_undef; + } + if (n.weakness() < 10 /* MAX_WEAKENSS */) { bool has_new_child = false; SASSERT(m_weak_abs); @@ -3933,6 +4114,11 @@ bool context::create_children(pob& n, datalog::rule const& r, !mdl.is_true(n.post()))) { kid->reset_derivation(); } + if (kid->is_may_pob()) { + SASSERT(n.get_gas() > 0); + n.set_gas(n.get_gas() - 1); + kid->set_gas(n.get_gas() - 1); + } out.push_back(kid); m_stats.m_num_queries++; return true; @@ -3971,6 +4157,17 @@ void context::collect_statistics(statistics& st) const st.update("SPACER num lemmas", m_stats.m_num_lemmas); // -- number of restarts taken st.update("SPACER restarts", m_stats.m_num_restarts); + // -- number of time pob abstraction was invoked + st.update("SPACER conj", m_stats.m_num_conj); + st.update("SPACER conj success", m_stats.m_num_conj_success); + st.update("SPACER conj failed", + m_stats.m_num_conj_failed); + st.update("SPACER pob out of gas", m_stats.m_num_pob_ofg); + st.update("SPACER subsume pob", m_stats.m_num_subsume_pobs); + st.update("SPACER subsume failed", m_stats.m_num_subsume_pob_reachable); + st.update("SPACER subsume success", m_stats.m_num_subsume_pob_blckd); + st.update("SPACER concretize", m_stats.m_num_concretize); + st.update("SPACER non local gen", m_stats.m_non_local_gen); // -- time to initialize the rules st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ()); @@ -3991,6 +4188,7 @@ void context::collect_statistics(statistics& st) const for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { m_lemma_generalizers[i]->collect_statistics(st); } + m_lmma_cluster->collect_statistics(st); } void context::reset_statistics() @@ -4008,6 +4206,7 @@ void context::reset_statistics() m_lemma_generalizers[i]->reset_statistics(); } + m_lmma_cluster->reset_statistics(); m_init_rules_watch.reset (); m_solve_watch.reset (); m_propagate_watch.reset (); @@ -4093,8 +4292,6 @@ void context::add_constraint (expr *c, unsigned level) } void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { - if (m_params.spacer_print_json().is_non_empty_string()) - m_json_marshaller.register_lemma(lem); bool handle=false; for (unsigned i = 0; i < m_callbacks.size(); i++) { handle|=m_callbacks[i]->new_lemma(); @@ -4116,10 +4313,7 @@ void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { } } -void context::new_pob_eh(pob *p) { - if (m_params.spacer_print_json().is_non_empty_string()) - m_json_marshaller.register_pob(p); -} +void context::new_pob_eh(pob *p) { } bool context::is_inductive() { // check that inductive level (F infinity) of the query predicate @@ -4140,6 +4334,10 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const if (n1.depth() != n2.depth()) { return n1.depth() < n2.depth(); } + if (n1.is_subsume() != n2.is_subsume()) { return n1.is_subsume(); } + if (n1.is_conjecture() != n2.is_conjecture()) { return n1.is_conjecture(); } + + if (n1.get_gas() != n2.get_gas()) { return n1.get_gas() > n2.get_gas(); } // -- a more deterministic order of proof obligations in a queue // if (!n1.get_context ().get_params ().spacer_nondet_tie_break ()) { @@ -4193,5 +4391,27 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const } - +// set gas of each may parent to 0 +// TODO: close siblings as well. kids of a pob are not stored in the pob +void context::close_all_may_parents(pob_ref node) { + pob_ref_vector to_do; + to_do.push_back(node.get()); + while (to_do.size() != 0) { + pob_ref t = to_do.back(); + t->set_gas(0); + if (t->is_may_pob()) { + t->close(); + } else + break; + to_do.pop_back(); + to_do.push_back(t->parent()); + } +} +// construct a simplified version of the post +void pob::get_post_simplified(expr_ref_vector &pob_cube) { + pob_cube.reset(); + pob_cube.push_back(m_post); + flatten_and(pob_cube); + simplify_bounds(pob_cube); +} } diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index b62da0766..1283a525f 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -21,22 +21,24 @@ Notes: --*/ #pragma once +// clang-format off -#include -#include #include +#include +#include -#include "util/scoped_ptr_vector.h" +#include "muz/spacer/spacer_cluster.h" #include "muz/spacer/spacer_manager.h" #include "muz/spacer/spacer_prop_solver.h" -#include "muz/spacer/spacer_json.h" +#include "muz/spacer/spacer_sem_matcher.h" +#include "util/scoped_ptr_vector.h" #include "muz/base/fp_params.hpp" namespace datalog { - class rule_set; - class context; -}; +class rule_set; +class context; +}; // namespace datalog namespace spacer { @@ -46,9 +48,12 @@ class pred_transformer; class derivation; class pob_queue; class context; +class lemma_cluster; +class lemma_cluster_finder; +class lemma_global_generalizer; -typedef obj_map rule2inst; -typedef obj_map decl2rel; +typedef obj_map rule2inst; +typedef obj_map decl2rel; class pob; typedef ref pob_ref; @@ -59,6 +64,10 @@ class reach_fact; typedef ref reach_fact_ref; typedef sref_vector reach_fact_ref_vector; +class lemma; +using lemma_ref = ref; +using lemma_ref_vector = sref_vector; + class reach_fact { unsigned m_ref_count; @@ -73,46 +82,44 @@ class reach_fact { bool m_init; -public: - reach_fact (ast_manager &m, const datalog::rule &rule, - expr* fact, const ptr_vector &aux_vars, - bool init = false) : - m_ref_count (0), m_fact (fact, m), m_aux_vars (aux_vars), - m_rule(rule), m_tag(m), m_init (init) {} - reach_fact (ast_manager &m, const datalog::rule &rule, - expr* fact, bool init = false) : - m_ref_count (0), m_fact (fact, m), m_rule(rule), m_tag(m), m_init (init) {} + public: + reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact, + const ptr_vector &aux_vars, bool init = false) + : m_ref_count(0), m_fact(fact, m), m_aux_vars(aux_vars), m_rule(rule), + m_tag(m), m_init(init) {} + reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact, + bool init = false) + : m_ref_count(0), m_fact(fact, m), m_rule(rule), m_tag(m), + m_init(init) {} - bool is_init () {return m_init;} - const datalog::rule& get_rule () {return m_rule;} + bool is_init() { return m_init; } + const datalog::rule &get_rule() { return m_rule; } - void add_justification (reach_fact *f) {m_justification.push_back (f);} - const reach_fact_ref_vector& get_justifications () {return m_justification;} + void add_justification(reach_fact *f) { m_justification.push_back(f); } + const reach_fact_ref_vector &get_justifications() { + return m_justification; + } - expr *get () {return m_fact.get ();} - const ptr_vector &aux_vars () {return m_aux_vars;} + expr *get() { return m_fact.get(); } + const ptr_vector &aux_vars() { return m_aux_vars; } - app* tag() const {SASSERT(m_tag); return m_tag;} - void set_tag(app* tag) {m_tag = tag;} + app *tag() const { + SASSERT(m_tag); + return m_tag; + } + void set_tag(app *tag) { m_tag = tag; } - void inc_ref () {++m_ref_count;} - void dec_ref () - { - SASSERT (m_ref_count > 0); - --m_ref_count; - if(m_ref_count == 0) { dealloc(this); } - } + void inc_ref() { ++m_ref_count; } + void dec_ref() { + SASSERT(m_ref_count > 0); + --m_ref_count; + if (m_ref_count == 0) { dealloc(this); } + } }; - -class lemma; -typedef ref lemma_ref; -typedef sref_vector lemma_ref_vector; - -typedef pob pob; - // a lemma class lemma { + // clang-format off unsigned m_ref_count; ast_manager &m; @@ -129,74 +136,75 @@ class lemma { unsigned m_external:1; // external lemma from another solver unsigned m_blocked:1; // blocked by CTP unsigned m_background:1; // background assumed fact + // clang-format on + // clang-format off void mk_expr_core(); void mk_cube_core(); -public: - lemma(ast_manager &manager, expr * fml, unsigned lvl); + + public: + lemma(ast_manager &manager, expr *fml, unsigned lvl); lemma(pob_ref const &p); lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl); -// lemma(const lemma &other) = delete; + // lemma(const lemma &other) = delete; - ast_manager &get_ast_manager() {return m;} + ast_manager &get_ast_manager() { return m; } - model_ref& get_ctp() {return m_ctp;} - bool has_ctp() {return !is_inductive() && m_ctp;} - void set_ctp(model_ref &v) {m_ctp = v;} - void reset_ctp() {m_ctp.reset();} + model_ref &get_ctp() { return m_ctp; } + bool has_ctp() { return !is_inductive() && m_ctp; } + void set_ctp(model_ref &v) { m_ctp = v; } + void reset_ctp() { m_ctp.reset(); } - void bump() {m_bumped++;} - unsigned get_bumped() {return m_bumped;} + void bump() { m_bumped++; } + unsigned get_bumped() { return m_bumped; } expr *get_expr(); bool is_false(); expr_ref_vector const &get_cube(); void update_cube(pob_ref const &p, expr_ref_vector &cube); - bool has_pob() {return !!m_pob;} - pob_ref &get_pob() {return m_pob;} - unsigned weakness() {return m_weakness;} + bool has_pob() { return !!m_pob; } + pob_ref &get_pob() { return m_pob; } + unsigned weakness() { return m_weakness; } - void add_skolem(app *zk, app* b); + void add_skolem(app *zk, app *b); - void set_external(bool ext){m_external = ext;} - bool external() { return m_external;} + void set_external(bool ext) { m_external = ext; } + bool external() { return m_external; } - void set_background(bool v) {m_background = v;} - bool is_background() {return m_background;} + void set_background(bool v) { m_background = v; } + bool is_background() { return m_background; } - bool is_blocked() {return m_blocked;} - void set_blocked(bool v) {m_blocked=v;} + bool is_blocked() { return m_blocked; } + void set_blocked(bool v) { m_blocked = v; } - bool is_inductive() const {return is_infty_level(m_lvl);} - unsigned level () const {return m_lvl;} - unsigned init_level() const {return m_init_lvl;} - void set_level (unsigned lvl); - app_ref_vector& get_bindings() {return m_bindings;} + bool is_inductive() const { return is_infty_level(m_lvl); } + unsigned level() const { return m_lvl; } + unsigned init_level() const { return m_init_lvl; } + void set_level(unsigned lvl); + app_ref_vector &get_bindings() { return m_bindings; } bool has_binding(app_ref_vector const &binding); void add_binding(app_ref_vector const &binding); - void instantiate(expr * const * exprs, expr_ref &result, expr *e = nullptr); - void mk_insts(expr_ref_vector& inst, expr* e = nullptr); - bool is_ground () {return !is_quantifier (get_expr());} + void instantiate(expr *const *exprs, expr_ref &result, expr *e = nullptr); + void mk_insts(expr_ref_vector &inst, expr *e = nullptr); + bool is_ground() { return !is_quantifier(get_expr()); } - void inc_ref () {++m_ref_count;} - void dec_ref () { - SASSERT (m_ref_count > 0); + void inc_ref() { ++m_ref_count; } + void dec_ref() { + SASSERT(m_ref_count > 0); --m_ref_count; - if (m_ref_count == 0) {dealloc(this);} + if (m_ref_count == 0) { dealloc(this); } } }; struct lemma_lt_proc { - bool operator() (lemma *a, lemma *b) { - return (a->level () < b->level ()) || - (a->level () == b->level () && - ast_lt_proc() (a->get_expr (), b->get_expr ())); + bool operator()(lemma *a, lemma *b) { + return (a->level() < b->level()) || + (a->level() == b->level() && + ast_lt_proc()(a->get_expr(), b->get_expr())); } }; - - // // Predicate transformer state. // A predicate transformer corresponds to the @@ -206,12 +214,16 @@ struct lemma_lt_proc { class pred_transformer { struct stats { - unsigned m_num_propagations; // num of times lemma is pushed higher - unsigned m_num_invariants; // num of infty lemmas found - unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing - unsigned m_num_is_invariant; // num of times lemmas are pushed - unsigned m_num_lemma_level_jump; // lemma learned at higher level than expected + // clang-format off + unsigned m_num_propagations; // num of times lemma is pushed higher + unsigned m_num_invariants; // num of infty lemmas found + unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing + unsigned m_num_is_invariant; // num of times lemmas are pushed + unsigned m_num_lemma_level_jump; // lemma learned at higher level than + // expected unsigned m_num_reach_queries; + // clang-format on + // clang-format off stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } @@ -220,7 +232,8 @@ class pred_transformer { /// manager of the lemmas in all the frames #include "muz/spacer/spacer_legacy_frames.h" class frames { - private: + private: + // clang-format off pred_transformer &m_pt; // parent pred_transformer lemma_ref_vector m_pinned_lemmas; // all created lemmas lemma_ref_vector m_lemmas; // active lemmas @@ -229,49 +242,55 @@ class pred_transformer { bool m_sorted; // true if m_lemmas is sorted by m_lt lemma_lt_proc m_lt; // sort order for m_lemmas + // clang-format on + // clang-format off - void sort (); + void sort(); - public: - frames (pred_transformer &pt) : m_pt (pt), - m_size(0), m_sorted (true) {} - void simplify_formulas (); + public: + frames(pred_transformer &pt) : m_pt(pt), m_size(0), m_sorted(true) {} + void simplify_formulas(); - pred_transformer& pt() const {return m_pt;} - const lemma_ref_vector &lemmas() const {return m_lemmas;} + pred_transformer &pt() const { return m_pt; } + const lemma_ref_vector &lemmas() const { return m_lemmas; } - - void get_frame_lemmas (unsigned level, expr_ref_vector &out) const { + void get_frame_lemmas(unsigned level, expr_ref_vector &out) const { for (auto &lemma : m_lemmas) { if (lemma->level() == level) { out.push_back(lemma->get_expr()); } } } - void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out, - bool with_bg = false) const { + void get_frame_geq_lemmas(unsigned level, expr_ref_vector &out, + bool with_bg = false) const { for (auto &lemma : m_lemmas) { if (lemma->level() >= level) { out.push_back(lemma->get_expr()); } } if (with_bg) { - for (auto &lemma : m_bg_invs) - out.push_back(lemma->get_expr()); + for (auto &lemma : m_bg_invs) out.push_back(lemma->get_expr()); } } - const lemma_ref_vector& get_bg_invs() const {return m_bg_invs;} - unsigned size() const {return m_size;} - unsigned lemma_size() const {return m_lemmas.size ();} - unsigned bg_invs_size() const {return m_bg_invs.size();} + void get_frame_all_lemmas(lemma_ref_vector &out, + bool with_bg = false) const { + for (auto &lemma : m_lemmas) { out.push_back(lemma); } + if (with_bg) { + for (auto &lemma : m_bg_invs) out.push_back(lemma); + } + } + const lemma_ref_vector &get_bg_invs() const { return m_bg_invs; } + unsigned size() const { return m_size; } + unsigned lemma_size() const { return m_lemmas.size(); } + unsigned bg_invs_size() const { return m_bg_invs.size(); } - void add_frame() {m_size++;} - void inherit_frames (frames &other) { + void add_frame() { m_size++; } + void inherit_frames(frames &other) { for (auto &other_lemma : other.m_lemmas) { - lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), - other_lemma->get_expr(), - other_lemma->level()); + lemma_ref new_lemma = + alloc(lemma, m_pt.get_ast_manager(), + other_lemma->get_expr(), other_lemma->level()); new_lemma->add_binding(other_lemma->get_bindings()); add_lemma(new_lemma.get()); } @@ -279,9 +298,9 @@ class pred_transformer { m_bg_invs.append(other.m_bg_invs); } - bool add_lemma (lemma *new_lemma); - void propagate_to_infinity (unsigned level); - bool propagate_to_next_level (unsigned level); + bool add_lemma(lemma *new_lemma); + void propagate_to_infinity(unsigned level); + bool propagate_to_next_level(unsigned level); }; /** @@ -298,7 +317,7 @@ class pred_transformer { // Type for the map from post-conditions to pobs. The common // case is that each post-condition corresponds to a single // pob. Other cases are handled by expanding the buffer - typedef obj_map expr2pob_buffer; + typedef obj_map expr2pob_buffer; // parent predicate transformer pred_transformer &m_pt; @@ -308,78 +327,159 @@ class pred_transformer { // a store pob_ref_vector m_pinned; - public: + + public: pob_manager(pred_transformer &pt) : m_pt(pt) {} - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post, app_ref_vector const &b); + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, + app_ref_vector const &b); - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post) { + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { app_ref_vector b(m_pt.get_ast_manager()); - return mk_pob (parent, level, depth, post, b); + return mk_pob(parent, level, depth, post, b); } - unsigned size() const {return m_pinned.size();} + unsigned size() const { return m_pinned.size(); } - pob* find_pob(pob* parent, expr *post); + pob *find_pob(pob *parent, expr *post); }; class pt_rule { + // clang-format off const datalog::rule &m_rule; expr_ref m_trans; // ground version of m_rule ptr_vector m_auxs; // auxiliary variables in m_trans app_ref_vector m_reps; // map from fv in m_rule to ground constants app_ref m_tag; // a unique tag for the rule + // clang-format on + // clang-format off - public: - pt_rule(ast_manager &m, const datalog::rule &r) : - m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} + public: + pt_rule(ast_manager &m, const datalog::rule &r) + : m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} - const datalog::rule &rule() const {return m_rule;} + const datalog::rule &rule() const { return m_rule; } - void set_tag(expr *tag) {SASSERT(is_app(tag)); set_tag(to_app(tag));} - void set_tag(app* tag) {m_tag = tag;} - app* tag() const {return m_tag;} - ptr_vector &auxs() {return m_auxs;} - void set_auxs(ptr_vector &v) {m_auxs.reset(); m_auxs.append(v);} - void set_reps(app_ref_vector &v) {m_reps.reset(); m_reps.append(v);} + void set_tag(expr *tag) { + SASSERT(is_app(tag)); + set_tag(to_app(tag)); + } + void set_tag(app *tag) { m_tag = tag; } + app *tag() const { return m_tag; } + ptr_vector &auxs() { return m_auxs; } + void set_auxs(ptr_vector &v) { + m_auxs.reset(); + m_auxs.append(v); + } + void set_reps(app_ref_vector &v) { + m_reps.reset(); + m_reps.append(v); + } - void set_trans(expr_ref &v) {m_trans=v;} - expr* trans() const {return m_trans;} - bool is_init() const {return m_rule.get_uninterpreted_tail_size() == 0;} + void set_trans(expr_ref &v) { m_trans = v; } + expr *trans() const { return m_trans; } + bool is_init() const { + return m_rule.get_uninterpreted_tail_size() == 0; + } }; class pt_rules { - typedef obj_map rule2ptrule; - typedef obj_map tag2ptrule; + typedef obj_map rule2ptrule; + typedef obj_map tag2ptrule; typedef rule2ptrule::iterator iterator; rule2ptrule m_rules; tag2ptrule m_tags; - public: - ~pt_rules() {for (auto &kv : m_rules) {dealloc(kv.m_value);}} - bool find_by_rule(const datalog::rule &r, pt_rule* &ptr) { + public: + ~pt_rules() { + for (auto &kv : m_rules) { dealloc(kv.m_value); } + } + + bool find_by_rule(const datalog::rule &r, pt_rule *&ptr) { return m_rules.find(&r, ptr); } - bool find_by_tag(const expr* tag, pt_rule* &ptr) { + bool find_by_tag(const expr *tag, pt_rule *&ptr) { return m_tags.find(tag, ptr); } pt_rule &mk_rule(ast_manager &m, const datalog::rule &r) { return mk_rule(pt_rule(m, r)); } pt_rule &mk_rule(const pt_rule &v); - void set_tag(expr* tag, pt_rule &v) { + void set_tag(expr *tag, pt_rule &v) { pt_rule *p; VERIFY(find_by_rule(v.rule(), p)); p->set_tag(tag); m_tags.insert(tag, p); } - bool empty() {return m_rules.empty();} - iterator begin() {return m_rules.begin();} - iterator end() {return m_rules.end();} - + bool empty() { return m_rules.empty(); } + iterator begin() { return m_rules.begin(); } + iterator end() { return m_rules.end(); } }; + /// Clusters of lemmas + class cluster_db { + sref_vector m_clusters; + unsigned m_max_cluster_size; + + public: + cluster_db() : m_max_cluster_size(0) {} + unsigned get_max_cluster_size() const { return m_max_cluster_size; } + + /// Return the smallest cluster than can contain \p lemma + lemma_cluster *can_contain(const lemma_ref &lemma) { + unsigned sz = UINT_MAX; + lemma_cluster *res = nullptr; + for (auto *c : m_clusters) { + if (c->get_gas() > 0 && c->get_size() < sz && + c->can_contain(lemma)) { + res = c; + sz = res->get_size(); + } + } + return res; + } + + bool contains(const lemma_ref &lemma) { + for (auto *c : m_clusters) { + if (c->contains(lemma)) { return true; } + } + return false; + } + + /// The number of clusters with pattern \p pattern + unsigned clstr_count(const expr_ref &pattern) { + unsigned count = 0; + for (auto c : m_clusters) { + if (c->get_pattern() == pattern) count++; + } + return count; + } + + lemma_cluster *mk_cluster(const expr_ref &pattern) { + m_clusters.push_back(alloc(lemma_cluster, pattern)); + return m_clusters.back(); + } + + /// Return the smallest cluster containing \p lemma + lemma_cluster *get_cluster(const lemma_ref &lemma) { + unsigned sz = UINT_MAX; + lemma_cluster *res = nullptr; + for (auto *c : m_clusters) { + if (c->get_size() < sz && c->contains(lemma)) { + res = c; + sz = res->get_size(); + } + } + return res; + } + + lemma_cluster *get_cluster(const expr *pattern) { + for (lemma_cluster *lc : m_clusters) { + if (lc->get_pattern().get() == pattern) return lc; + } + return nullptr; + } + }; + // clang-format off manager& pm; // spacer::manager ast_manager& m; // ast_manager context& ctx; // spacer::context @@ -408,94 +508,102 @@ class pred_transformer { stopwatch m_ctp_watch; stopwatch m_mbp_watch; bool m_has_quantified_frame; // True when a quantified lemma is in the frame + cluster_db m_cluster_db; + // clang-format on + // clang-format off void init_sig(); app_ref mk_extend_lit(); void ensure_level(unsigned level); - void add_lemma_core (lemma *lemma, bool ground_only = false); - void add_lemma_from_child (pred_transformer &child, lemma *lemma, - unsigned lvl, bool ground_only = false); + void add_lemma_core(lemma *lemma, bool ground_only = false); + void add_lemma_from_child(pred_transformer &child, lemma *lemma, + unsigned lvl, bool ground_only = false); - void mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result); + void mk_assumptions(func_decl *head, expr *fml, expr_ref_vector &result); // Initialization - void init_rules(decl2rel const& pts); - void init_rule(decl2rel const& pts, datalog::rule const& rule); - void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, - expr_ref_vector& side, unsigned tail_idx); + void init_rules(decl2rel const &pts); + void init_rule(decl2rel const &pts, datalog::rule const &rule); + void init_atom(decl2rel const &pts, app *atom, app_ref_vector &var_reprs, + expr_ref_vector &side, unsigned tail_idx); - void simplify_formulas(tactic& tac, expr_ref_vector& fmls); + void simplify_formulas(tactic &tac, expr_ref_vector &fmls); - void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); + void add_premises(decl2rel const &pts, unsigned lvl, datalog::rule &rule, + expr_ref_vector &r); - app_ref mk_fresh_rf_tag (); + app_ref mk_fresh_rf_tag(); // get tagged formulae of all of the background invariants for all of the // predecessors of the current transformer void get_pred_bg_invs(expr_ref_vector &out); - const lemma_ref_vector &get_bg_invs() const {return m_frames.get_bg_invs();} + const lemma_ref_vector &get_bg_invs() const { + return m_frames.get_bg_invs(); + } -public: - pred_transformer(context& ctx, manager& pm, func_decl* head); + public: + pred_transformer(context &ctx, manager &pm, func_decl *head); - inline bool use_native_mbp (); + inline bool use_native_mbp(); bool mk_mdl_rf_consistent(const datalog::rule *r, model &mdl); - reach_fact *get_rf (expr *v) { + reach_fact *get_rf(expr *v) { for (auto *rf : m_reach_facts) { - if (v == rf->get()) {return rf;} + if (v == rf->get()) { return rf; } } return nullptr; } - void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; + void find_predecessors(datalog::rule const &r, + ptr_vector &predicates) const; - void add_rule(datalog::rule* r) {m_rules.push_back(r);} - void add_use(pred_transformer* pt) {if (!m_use.contains(pt)) {m_use.insert(pt);}} - void initialize(decl2rel const& pts); + void add_rule(datalog::rule *r) { m_rules.push_back(r); } + void add_use(pred_transformer *pt) { + if (!m_use.contains(pt)) { m_use.insert(pt); } + } + void initialize(decl2rel const &pts); - func_decl* head() const {return m_head;} - ptr_vector const& rules() const {return m_rules;} - func_decl* sig(unsigned i) const {return m_sig[i];} // signature - func_decl* const* sig() {return m_sig.data();} - unsigned sig_size() const {return m_sig.size();} - expr* transition() const {return m_transition;} - expr* init() const {return m_init;} - expr* rule2tag(datalog::rule const* r) { + func_decl *head() const { return m_head; } + ptr_vector const &rules() const { return m_rules; } + func_decl *sig(unsigned i) const { return m_sig[i]; } // signature + func_decl *const *sig() { return m_sig.data(); } + unsigned sig_size() const { return m_sig.size(); } + expr *transition() const { return m_transition; } + expr *init() const { return m_init; } + expr *rule2tag(datalog::rule const *r) { pt_rule *p; return m_pt_rules.find_by_rule(*r, p) ? p->tag() : nullptr; } - unsigned get_num_levels() const {return m_frames.size ();} - expr_ref get_cover_delta(func_decl* p_orig, int level); - void add_cover(unsigned level, expr* property, bool bg = false); + unsigned get_num_levels() const { return m_frames.size(); } + expr_ref get_cover_delta(func_decl *p_orig, int level); + void add_cover(unsigned level, expr *property, bool bg = false); expr_ref get_reachable(); - std::ostream& display(std::ostream& strm) const; + std::ostream &display(std::ostream &strm) const; - void collect_statistics(statistics& st) const; + void collect_statistics(statistics &st) const; void reset_statistics(); - bool is_must_reachable(expr* state, model_ref* model = nullptr); + bool is_must_reachable(expr *state, model_ref *model = nullptr); /// \brief Returns reachability fact active in the given model /// all determines whether initial reachability facts are included as well - reach_fact *get_used_rf(model& mdl, bool all = true); + reach_fact *get_used_rf(model &mdl, bool all = true); /// \brief Returns reachability fact active in the origin of the given model - reach_fact* get_used_origin_rf(model &mdl, unsigned oidx); + reach_fact *get_used_origin_rf(model &mdl, unsigned oidx); /// \brief Collects all the reachable facts used in mdl - void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector& res); + void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector &res); void get_all_used_rf(model &mdl, reach_fact_ref_vector &res); - expr_ref get_origin_summary(model &mdl, - unsigned level, unsigned oidx, bool must, - const ptr_vector **aux); + expr_ref get_origin_summary(model &mdl, unsigned level, unsigned oidx, + bool must, const ptr_vector **aux); bool is_ctp_blocked(lemma *lem); const datalog::rule *find_rule(model &mdl); - const datalog::rule *find_rule(model &mev, bool& is_concrete, - bool_vector& reach_pred_used, - unsigned& num_reuse_reach); - expr* get_transition(datalog::rule const& r) { + const datalog::rule *find_rule(model &mev, bool &is_concrete, + bool_vector &reach_pred_used, + unsigned &num_reuse_reach); + expr *get_transition(datalog::rule const &r) { pt_rule *p; return m_pt_rules.find_by_rule(r, p) ? p->trans() : nullptr; } - ptr_vector& get_aux_vars(datalog::rule const& r) { + ptr_vector &get_aux_vars(datalog::rule const &r) { pt_rule *p = nullptr; VERIFY(m_pt_rules.find_by_rule(r, p)); return p->auxs(); @@ -504,71 +612,71 @@ public: bool propagate_to_next_level(unsigned level); void propagate_to_infinity(unsigned level); /// \brief Add a lemma to the current context and all users - bool add_lemma(expr * e, unsigned lvl, bool bg); - bool add_lemma(lemma* lem) {return m_frames.add_lemma(lem);} - expr* get_reach_case_var (unsigned idx) const; - bool has_rfs () const { return !m_reach_facts.empty () ;} + bool add_lemma(expr *e, unsigned lvl, bool bg); + bool add_lemma(lemma *lem) { return m_frames.add_lemma(lem); } + expr *get_reach_case_var(unsigned idx) const; + bool has_rfs() const { return !m_reach_facts.empty(); } /// initialize reachability facts using initial rules - void init_rfs (); + void init_rfs(); reach_fact *mk_rf(pob &n, model &mdl, const datalog::rule &r); - void add_rf (reach_fact *fact, bool force = false); // add reachability fact - reach_fact* get_last_rf () const { return m_reach_facts.back (); } - expr* get_last_rf_tag () const; + void add_rf(reach_fact *fact, bool force = false); // add reachability fact + reach_fact *get_last_rf() const { return m_reach_facts.back(); } + expr *get_last_rf_tag() const; - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post, app_ref_vector const &b){ + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, + app_ref_vector const &b) { return m_pobs.mk_pob(parent, level, depth, post, b); } - pob* find_pob(pob *parent, expr *post) { + pob *find_pob(pob *parent, expr *post) { return m_pobs.find_pob(parent, post); } - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post) { + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { return m_pobs.mk_pob(parent, level, depth, post); } lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model, unsigned& uses_level, bool& is_concrete, - datalog::rule const*& r, - bool_vector& reach_pred_used, - unsigned& num_reuse_reach); - bool is_invariant(unsigned level, lemma* lem, - unsigned& solver_level, + datalog::rule const *&r, bool_vector &reach_pred_used, + unsigned& num_reuse_reach, bool use_iuc = true); + bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level, expr_ref_vector* core = nullptr); - bool is_invariant(unsigned level, expr* lem, - unsigned& solver_level, expr_ref_vector* core = nullptr) { + bool is_invariant(unsigned level, expr *lem, unsigned &solver_level, + expr_ref_vector *core = nullptr) { // XXX only needed for legacy_frames to compile - UNREACHABLE(); return false; + UNREACHABLE(); + return false; } - bool check_inductive(unsigned level, expr_ref_vector& state, - unsigned& assumes_level, unsigned weakness = UINT_MAX); + bool check_inductive(unsigned level, expr_ref_vector &state, + unsigned &assumes_level, unsigned weakness = UINT_MAX); expr_ref get_formulas(unsigned level, bool bg = false) const; void simplify_formulas(); - context& get_context () const {return ctx;} - manager& get_manager() const {return pm;} - ast_manager& get_ast_manager() const {return m;} + context &get_context() const { return ctx; } + manager &get_manager() const { return pm; } + ast_manager &get_ast_manager() const { return m; } - void add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r); + void add_premises(decl2rel const &pts, unsigned lvl, expr_ref_vector &r); - void inherit_lemmas(pred_transformer& other); + void inherit_lemmas(pred_transformer &other); - void ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars, - bool is_init); + void ground_free_vars(expr *e, app_ref_vector &vars, + ptr_vector &aux_vars, bool is_init); /// \brief Adds a given expression to the set of initial rules - app* extend_initial (expr *e); + app *extend_initial(expr *e); - /// \brief Returns true if the obligation is already blocked by current lemmas - bool is_blocked (pob &n, unsigned &uses_level); - /// \brief Returns true if the obligation is already blocked by current quantified lemmas - bool is_qblocked (pob &n); + /// \brief Returns true if the obligation is already blocked by current + /// lemmas + bool is_blocked(pob &n, unsigned &uses_level, model_ref *model = nullptr); + /// \brief Returns true if the obligation is already blocked by current + /// quantified lemmas + bool is_qblocked(pob &n); /// \brief interface to Model Based Projection void mbp(app_ref_vector &vars, expr_ref &fml, model &mdl, @@ -577,19 +685,48 @@ public: void updt_solver(prop_solver *solver); void updt_solver_with_lemmas(prop_solver *solver, - const pred_transformer &pt, - app *rule_tag, unsigned pos); - void update_solver_with_rfs(prop_solver *solver, - const pred_transformer &pt, - app *rule_tag, unsigned pos); + const pred_transformer &pt, app *rule_tag, + unsigned pos); + // exposing ACTIVE lemmas (alternatively, one can expose `m_pinned_lemmas` + // for ALL lemmas) + void get_all_lemmas(lemma_ref_vector &out, bool with_bg = false) const { + m_frames.get_frame_all_lemmas(out, with_bg); + } + void update_solver_with_rfs(prop_solver *solver, const pred_transformer &pt, + app *rule_tag, unsigned pos); + lemma_cluster *mk_cluster(const expr_ref &pattern) { + return m_cluster_db.mk_cluster(pattern); + } + + // Checks whether \p lemma is in any existing cluster + bool clstr_contains(const lemma_ref &lemma) { + return m_cluster_db.contains(lemma); + } + + /// The number of clusters with pattern \p pattern + unsigned clstr_count(const expr_ref &pattern) { + return m_cluster_db.clstr_count(pattern); + } + + /// Checks whether \p lemma matches any cluster + lemma_cluster *clstr_match(const lemma_ref &lemma) { + lemma_cluster *res = m_cluster_db.get_cluster(lemma); + if (!res) res = m_cluster_db.can_contain(lemma); + return res; + } + + /// Returns a cluster with pattern \p pattern + lemma_cluster *get_cluster(const expr *pattern) { + return m_cluster_db.get_cluster(pattern); + } }; - /** * A proof obligation. */ class pob { + // clang-format off // TBD: remove this friend class context; unsigned m_ref_count; @@ -608,12 +745,25 @@ class pob { unsigned m_level:16; unsigned m_depth:16; + unsigned m_desired_level:16; + /// whether a concrete answer to the post is found unsigned m_open:1; - /// whether to use farkas generalizer to construct a lemma blocking this node + /// whether to use farkas generalizer to construct a lemma blocking this + /// node unsigned m_use_farkas:1; /// true if this pob is in pob_queue unsigned m_in_queue:1; + // true if this pob is a conjecture + unsigned m_is_conjecture:1; + // should do local generalizations on pob + unsigned m_enable_local_gen:1; + // should concretize cube + unsigned m_enable_concretize:1; + // is a subsume pob + unsigned m_is_subsume:1; + // should apply expand bnd generalization on pob + unsigned m_enable_expand_bnd_gen:1; unsigned m_weakness; /// derivation representing the position of this node in the parent's rule @@ -625,101 +775,141 @@ class pob { // lemmas created to block this pob (at any time, not necessarily active) ptr_vector m_lemmas; - // depth -> watch - std::map m_expand_watches; unsigned m_blocked_lvl; + // clang-format on + // clang-format off + // pattern identified for one of its lemmas + expr_ref m_concretize_pat; + + // gas decides how much time is spent in blocking this (may) pob + unsigned m_gas; + + // additional data used by global (and other) generalizations + scoped_ptr m_data; public: - pob (pob* parent, pred_transformer& pt, - unsigned level, unsigned depth=0, bool add_to_parent=true); + pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0, + bool add_to_parent = true); - ~pob() {if (m_parent) { m_parent->erase_child(*this); }} + // no copy constructor + pob(const pob&) = delete; + // no move constructor + pob(pob &&) = delete; + + ~pob() { + if (m_parent) { m_parent->erase_child(*this); } + } + + void set_data(pob* v) { m_data = v; } + void reset_data() { set_data(nullptr); } + pob* get_data() { return m_data.get(); } + bool has_data() { return m_data; } // TBD: move into constructor and make private void set_post(expr *post, app_ref_vector const &binding); void set_post(expr *post); - unsigned weakness() {return m_weakness;} - void bump_weakness() {m_weakness++;} - void reset_weakness() {m_weakness=0;} + unsigned weakness() { return m_weakness; } + void bump_weakness() { m_weakness++; } + void reset_weakness() { m_weakness = 0; } - void inc_level () { + void inc_level() { SASSERT(!is_in_queue()); - m_level++; m_depth++;reset_weakness(); + m_level++; + m_depth++; + reset_weakness(); } void inherit(pob const &p); - void set_derivation (derivation *d) {m_derivation = d;} - bool has_derivation () const {return (bool)m_derivation;} - derivation &get_derivation() const {return *m_derivation.get ();} - void reset_derivation () {set_derivation (nullptr);} + void set_derivation(derivation *d) { m_derivation = d; } + bool has_derivation() const { return (bool)m_derivation; } + derivation &get_derivation() const { return *m_derivation.get (); } + void reset_derivation() { set_derivation (nullptr); } /// detaches derivation from the node without deallocating - derivation* detach_derivation () {return m_derivation.detach ();} + derivation* detach_derivation() { return m_derivation.detach (); } - pob* parent () const { return m_parent.get (); } + pob* parent() const { return m_parent.get (); } - pred_transformer& pt () const { return m_pt; } - ast_manager& get_ast_manager () const { return m_pt.get_ast_manager (); } - manager& get_manager () const { return m_pt.get_manager (); } - context& get_context () const {return m_pt.get_context ();} + bool is_conjecture() const { return m_is_conjecture; } + void set_conjecture(bool v = true) { m_is_conjecture = v; } - unsigned level () const { return m_level; } - unsigned depth () const {return m_depth;} - unsigned width () const {return m_kids.size();} - unsigned blocked_at(unsigned lvl=0){ + void disable_expand_bnd_gen() { m_enable_expand_bnd_gen = false; } + bool is_expand_bnd_enabled() { return m_enable_expand_bnd_gen; } + void set_expand_bnd(bool v = true) { m_enable_expand_bnd_gen = v; } + void set_concretize_pattern(const expr_ref &pattern) { m_concretize_pat = pattern; } + const expr_ref &get_concretize_pattern() const { return m_concretize_pat; } + bool is_subsume() const { return m_is_subsume; } + void set_subsume(bool v = true) { m_is_subsume = v; } + bool is_may_pob() const { return is_subsume() || is_conjecture(); } + unsigned get_gas() const { return m_gas; } + void set_gas(unsigned n) { m_gas = n; } + + bool is_local_gen_enabled() const { return m_enable_local_gen; } + void disable_local_gen() { m_enable_local_gen = false; } + void get_post_simplified(expr_ref_vector &res); + bool is_concretize_enabled() const { return m_enable_concretize && m_gas > 0; } + void set_concretize(bool v = true) { m_enable_concretize = v; } + pred_transformer& pt() const { return m_pt; } + ast_manager& get_ast_manager() const { return m_pt.get_ast_manager(); } + manager& get_manager() const { return m_pt.get_manager(); } + context& get_context() const { return m_pt.get_context(); } + + unsigned level() const { return m_level; } + unsigned depth() const { return m_depth; } + unsigned desired_level() const { return m_desired_level; } + void set_desired_level(unsigned v) { m_desired_level = v; } + unsigned width() const { return m_kids.size(); } + unsigned blocked_at(unsigned lvl = 0) { return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); } - bool is_in_queue() const {return m_in_queue;} - void set_in_queue(bool v) {m_in_queue = v;} - bool use_farkas_generalizer () const {return m_use_farkas;} - void set_farkas_generalizer (bool v) {m_use_farkas = v;} + bool is_in_queue() const { return m_in_queue; } + void set_in_queue(bool v) { m_in_queue = v; } + bool use_farkas_generalizer() const { return m_use_farkas; } + void set_farkas_generalizer(bool v) { m_use_farkas = v; } - expr* post() const { return m_post.get (); } + expr *post() const { return m_post.get(); } - bool is_closed () const { return !m_open; } + bool is_closed() const { return !m_open; } void close(); - const ptr_vector &children() const {return m_kids;} - void add_child (pob &v) {m_kids.push_back (&v);} - void erase_child (pob &v) {m_kids.erase (&v);} + const ptr_vector &children() const { return m_kids; } + void add_child(pob &v) { m_kids.push_back(&v); } + void erase_child(pob &v) { m_kids.erase(&v); } - const ptr_vector &lemmas() const {return m_lemmas;} - void add_lemma(lemma* new_lemma) {m_lemmas.push_back(new_lemma);} + const ptr_vector &lemmas() const { return m_lemmas; } + void add_lemma(lemma *new_lemma) { m_lemmas.push_back(new_lemma); } - bool is_ground () const { return m_binding.empty (); } + bool is_ground() const { return m_binding.empty(); } unsigned get_free_vars_size() const { return m_binding.size(); } - app_ref_vector const &get_binding() const {return m_binding;} + app_ref_vector const &get_binding() const { return m_binding; } /* * Returns a map from variable id to skolems that implicitly * represent them in the pob. Note that only some (or none) of the * skolems returned actually appear in the post of the pob. */ - void get_skolems(app_ref_vector& v); + void get_skolems(app_ref_vector &v); void on_expand() { - m_expand_watches[m_depth].start(); - if (m_parent.get()){m_parent.get()->on_expand();} + if (m_parent.get()) { m_parent.get()->on_expand(); } } void off_expand() { - m_expand_watches[m_depth].stop(); - if (m_parent.get()){m_parent.get()->off_expand();} + if (m_parent.get()) { m_parent.get()->off_expand(); } } - double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds();} - void inc_ref () {++m_ref_count;} - void dec_ref () { + void inc_ref() { ++m_ref_count; } + void dec_ref() { --m_ref_count; - if (m_ref_count == 0) {dealloc(this);} + if (m_ref_count == 0) { dealloc(this); } } std::ostream &display(std::ostream &out, bool full = false) const; - class on_expand_event - { + class on_expand_event { pob &m_p; - public: - on_expand_event(pob &p) : m_p(p) {m_p.on_expand();} - ~on_expand_event() {m_p.off_expand();} + + public: + on_expand_event(pob &p) : m_p(p) { m_p.on_expand(); } + ~on_expand_event() { m_p.off_expand(); } }; }; @@ -728,11 +918,11 @@ inline std::ostream &operator<<(std::ostream &out, pob const &p) { } struct pob_lt_proc { - bool operator() (const pob *pn1, const pob *pn2) const; + bool operator()(const pob *pn1, const pob *pn2) const; }; struct pob_gt_proc { - bool operator() (const pob *n1, const pob *n2) const { + bool operator()(const pob *n1, const pob *n2) const { return pob_lt_proc()(n2, n1); } }; @@ -751,23 +941,23 @@ class derivation { bool m_must; app_ref_vector m_ovars; - public: - premise (pred_transformer &pt, unsigned oidx, expr *summary, bool must, - const ptr_vector *aux_vars = nullptr); + public: + premise(pred_transformer &pt, unsigned oidx, expr *summary, bool must, + const ptr_vector *aux_vars = nullptr); - bool is_must() {return m_must;} - expr * get_summary() {return m_summary.get ();} - app_ref_vector &get_ovars() {return m_ovars;} - unsigned get_oidx() {return m_oidx;} - pred_transformer &pt() {return m_pt;} + bool is_must() { return m_must; } + expr *get_summary() { return m_summary.get(); } + app_ref_vector &get_ovars() { return m_ovars; } + unsigned get_oidx() { return m_oidx; } + pred_transformer &pt() { return m_pt; } /// \brief Updated the summary. /// The new summary is over n-variables. - void set_summary(expr * summary, bool must, + void set_summary(expr *summary, bool must, const ptr_vector *aux_vars = nullptr); }; - + // clang-format off /// parent model node pob& m_parent; @@ -782,54 +972,58 @@ class derivation { expr_ref m_trans; // implicitly existentially quantified variables in m_trans app_ref_vector m_evars; + // clang-format on + // clang-format off + /// -- create next child using given model as the guide /// -- returns NULL if there is no next child - pob* create_next_child (model &mdl); + pob *create_next_child(model &mdl); /// existentially quantify vars and skolemize the result void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res); -public: - derivation (pob& parent, datalog::rule const& rule, - expr *trans, app_ref_vector const &evars); - void add_premise (pred_transformer &pt, unsigned oidx, - expr * summary, bool must, const ptr_vector *aux_vars = nullptr); + + public: + derivation(pob &parent, datalog::rule const &rule, expr *trans, + app_ref_vector const &evars); + void add_premise(pred_transformer &pt, unsigned oidx, expr *summary, + bool must, const ptr_vector *aux_vars = nullptr); /// creates the first child. Must be called after all the premises /// are added. The model must be valid for the premises /// Returns NULL if no child exits - pob *create_first_child (model &mdl); + pob *create_first_child(model &mdl); /// Create the next child. Must summary of the currently active /// premise must be consistent with the transition relation - pob *create_next_child (); + pob *create_next_child(); - datalog::rule const& get_rule () const { return m_rule; } - pob& get_parent () const { return m_parent; } - ast_manager &get_ast_manager () const {return m_parent.get_ast_manager ();} - manager &get_manager () const {return m_parent.get_manager ();} - context &get_context() const {return m_parent.get_context();} - pred_transformer &pt() const {return m_parent.pt();} + datalog::rule const &get_rule() const { return m_rule; } + pob &get_parent() const { return m_parent; } + ast_manager &get_ast_manager() const { return m_parent.get_ast_manager(); } + manager &get_manager() const { return m_parent.get_manager(); } + context &get_context() const { return m_parent.get_context(); } + pred_transformer &pt() const { return m_parent.pt(); } }; - class pob_queue { - typedef std::priority_queue, pob_gt_proc> pob_queue_ty; - pob_ref m_root; + typedef std::priority_queue, pob_gt_proc> + pob_queue_ty; + pob_ref m_root; unsigned m_max_level; unsigned m_min_depth; - pob_queue_ty m_data; + pob_queue_ty m_data; -public: - pob_queue(): m_root(nullptr), m_max_level(0), m_min_depth(0) {} + public: + pob_queue() : m_root(nullptr), m_max_level(0), m_min_depth(0) {} void reset(); - pob* top(); + pob *top(); void pop(); - void push (pob &n); + void push(pob &n); - void inc_level () { - SASSERT (!m_data.empty () || m_root); + void inc_level() { + SASSERT(!m_data.empty() || m_root); m_max_level++; m_min_depth++; if (m_root && m_data.empty()) { @@ -839,36 +1033,35 @@ public: } } - pob& get_root() const {return *m_root.get ();} - void set_root(pob& n); - bool is_root(pob& n) const {return m_root.get () == &n;} + pob &get_root() const { return *m_root.get(); } + void set_root(pob &n); + bool is_root(pob &n) const { return m_root.get() == &n; } - unsigned max_level() const {return m_max_level;} - unsigned min_depth() const {return m_min_depth;} - size_t size() const {return m_data.size();} + unsigned max_level() const { return m_max_level; } + unsigned min_depth() const { return m_min_depth; } + size_t size() const { return m_data.size(); } }; - /** * Generalizers (strengthens) a lemma */ class lemma_generalizer { -protected: - context& m_ctx; -public: - lemma_generalizer(context& ctx): m_ctx(ctx) {} - virtual ~lemma_generalizer() {} + protected: + context &m_ctx; + + public: + lemma_generalizer(context &ctx) : m_ctx(ctx) {} + virtual ~lemma_generalizer() = default; virtual void operator()(lemma_ref &lemma) = 0; - virtual void collect_statistics(statistics& st) const {} + virtual void collect_statistics(statistics &st) const {} virtual void reset_statistics() {} }; - class spacer_callback { -protected: + protected: context &m_context; -public: + public: spacer_callback(context &context) : m_context(context) {} virtual ~spacer_callback() = default; @@ -890,14 +1083,13 @@ public: virtual inline bool propagate() { return false; } virtual void propagate_eh() {} - }; // order in which children are processed enum spacer_children_order { - CO_RULE, // same order as in the rule - CO_REV_RULE, // reverse order of the rule - CO_RANDOM // random shuffle + CO_RULE, // same order as in the rule + CO_REV_RULE, // reverse order of the rule + CO_RANDOM // random shuffle }; class context { @@ -913,10 +1105,20 @@ class context { unsigned m_num_restarts; unsigned m_num_lemmas_imported; unsigned m_num_lemmas_discarded; + unsigned m_num_conj; + unsigned m_num_conj_success; + unsigned m_num_conj_failed; + unsigned m_num_subsume_pobs; + unsigned m_num_subsume_pob_reachable; + unsigned m_num_subsume_pob_blckd; + unsigned m_num_concretize; + unsigned m_num_pob_ofg; + unsigned m_non_local_gen; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; + // clang-format off // stat watches stopwatch m_solve_watch; stopwatch m_propagate_watch; @@ -925,7 +1127,7 @@ class context { stopwatch m_create_children_watch; stopwatch m_init_rules_watch; - fp_params const& m_params; + fp_params const& m_params; ast_manager& m; datalog::context* m_context; manager m_pm; @@ -935,7 +1137,6 @@ class context { scoped_ptr m_pool1; scoped_ptr m_pool2; - random_gen m_random; spacer_children_order m_children_order; decl2rel m_rels; // Map from relation predicate to fp-operator. @@ -946,6 +1147,9 @@ class context { unsigned m_inductive_lvl; unsigned m_expanded_lvl; ptr_buffer m_lemma_generalizers; + lemma_global_generalizer *m_global_gen; + lemma_generalizer *m_expand_bnd_gen; + lemma_cluster_finder *m_lmma_cluster; stats m_stats; model_converter_ref m_mc; proof_converter_ref m_pc; @@ -978,27 +1182,32 @@ class context { bool m_simplify_formulas_post; bool m_pdr_bfs; bool m_use_bg_invs; + bool m_global; + bool m_expand_bnd; + bool m_gg_conjecture; + bool m_gg_subsume; + bool m_gg_concretize; + bool m_use_iuc; unsigned m_push_pob_max_depth; unsigned m_max_level; unsigned m_restart_initial_threshold; unsigned m_blast_term_ite_inflation; scoped_ptr_vector m_callbacks; - json_marshaller m_json_marshaller; std::fstream* m_trace_stream; + // clang-format on + // clang-format off // Solve using gpdr strategy lbool gpdr_solve_core(); bool gpdr_check_reachability(unsigned lvl, model_search &ms); - bool gpdr_create_split_children(pob &n, const datalog::rule &r, - expr *trans, - model &mdl, - pob_ref_buffer &out); + bool gpdr_create_split_children(pob &n, const datalog::rule &r, expr *trans, + model &mdl, pob_ref_buffer &out); // progress logging void log_enter_level(unsigned lvl); void log_propagate(); void log_expand_pob(pob &); - void log_add_lemma(pred_transformer &, lemma&); + void log_add_lemma(pred_transformer &, lemma &); // Functions used by search. lbool solve_core(unsigned from_lvl = 0); @@ -1008,9 +1217,8 @@ class context { unsigned full_prop_lvl); bool is_reachable(pob &n); lbool expand_pob(pob &n, pob_ref_buffer &out); - bool create_children(pob& n, const datalog::rule &r, - model &mdl, - const bool_vector& reach_pred_used, + bool create_children(pob &n, const datalog::rule &r, model &mdl, + const bool_vector &reach_pred_used, pob_ref_buffer &out); /** @@ -1021,129 +1229,132 @@ class context { return expr_ref(pr.get(), pr.get_manager()); } expr_ref mk_unsat_answer() const; - unsigned get_cex_depth (); + unsigned get_cex_depth(); // Generate inductive property - void get_level_property(unsigned lvl, expr_ref_vector& res, - vector & rs, + void get_level_property(unsigned lvl, expr_ref_vector &res, + vector &rs, bool with_bg = false) const; - // Initialization void init_lemma_generalizers(); void reset_lemma_generalizers(); - void inherit_lemmas(const decl2rel& rels); + void inherit_lemmas(const decl2rel &rels); void init_global_smt_params(); - void init_rules(datalog::rule_set& rules, decl2rel& transformers); + void init_rules(datalog::rule_set &rules, decl2rel &transformers); // (re)initialize context with new relations void init(const decl2rel &rels); bool validate(); bool check_invariant(unsigned lvl); - bool check_invariant(unsigned lvl, func_decl* fn); + bool check_invariant(unsigned lvl, func_decl *fn); void checkpoint(); void simplify_formulas(); - void dump_json(); - void predecessor_eh(); void updt_params(); lbool handle_unknown(pob &n, const datalog::rule *r, model &model); bool mk_mdl_rf_consistent(model &mdl); -public: + public: /** - Initial values of predicates are stored in corresponding relations in dctx. - We check whether there is some reachable state of the relation checked_relation. + Initial values of predicates are stored in corresponding relations in + dctx. We check whether there is some reachable state of the relation + checked_relation. */ - context(fp_params const& params, ast_manager& m); + context(fp_params const ¶ms, ast_manager &m); ~context(); - const fp_params &get_params() const { return m_params; } - bool use_eq_prop() const {return m_use_eq_prop;} - bool use_native_mbp() const {return m_use_native_mbp;} - bool use_ground_pob() const {return m_ground_pob;} - bool use_instantiate() const {return m_instantiate;} - bool weak_abs() const {return m_weak_abs;} - bool use_qlemmas() const {return m_use_qlemmas;} - bool use_euf_gen() const {return m_use_euf_gen;} - bool use_lim_num_gen() const {return m_use_lim_num_gen;} - bool simplify_pob() const {return m_simplify_pob;} - bool use_ctp() const {return m_use_ctp;} - bool use_inc_clause() const {return m_use_inc_clause;} - unsigned blast_term_ite_inflation() const {return m_blast_term_ite_inflation;} - bool elim_aux() const {return m_elim_aux;} - bool reach_dnf() const {return m_reach_dnf;} - bool use_bg_invs() const {return m_use_bg_invs;} + bool use_eq_prop() const { return m_use_eq_prop; } + bool use_native_mbp() const { return m_use_native_mbp; } + bool use_ground_pob() const { return m_ground_pob; } + bool use_instantiate() const { return m_instantiate; } + bool weak_abs() const { return m_weak_abs; } + bool use_qlemmas() const { return m_use_qlemmas; } + bool use_euf_gen() const { return m_use_euf_gen; } + bool use_lim_num_gen() const { return m_use_lim_num_gen; } + bool simplify_pob() const { return m_simplify_pob; } + bool use_ctp() const { return m_use_ctp; } + bool use_inc_clause() const { return m_use_inc_clause; } + unsigned blast_term_ite_inflation() const { + return m_blast_term_ite_inflation; + } + bool elim_aux() const { return m_elim_aux; } + bool reach_dnf() const { return m_reach_dnf; } + bool use_bg_invs() const { return m_use_bg_invs; } + bool do_subsume() const { return m_gg_subsume; } - ast_manager& get_ast_manager() const {return m;} - manager& get_manager() {return m_pm;} - const manager & get_manager() const {return m_pm;} - decl2rel const& get_pred_transformers() const {return m_rels;} - pred_transformer& get_pred_transformer(func_decl* p) const {return *m_rels.find(p);} - - datalog::context& get_datalog_context() const { - SASSERT(m_context); return *m_context; + ast_manager &get_ast_manager() const { return m; } + manager &get_manager() { return m_pm; } + const manager &get_manager() const { return m_pm; } + decl2rel const &get_pred_transformers() const { return m_rels; } + pred_transformer &get_pred_transformer(func_decl *p) const { + return *m_rels.find(p); } - void update_rules(datalog::rule_set& rules); + datalog::context &get_datalog_context() const { + SASSERT(m_context); + return *m_context; + } + + void update_rules(datalog::rule_set &rules); lbool solve(unsigned from_lvl = 0); - lbool solve_from_lvl (unsigned from_lvl); + lbool solve_from_lvl(unsigned from_lvl); - - expr_ref get_answer(); + expr_ref get_answer(); /** * get bottom-up (from query) sequence of ground predicate instances * (for e.g. P(0,1,0,0,3)) that together form a ground derivation to query */ - expr_ref get_ground_sat_answer () const; + expr_ref get_ground_sat_answer() const; proof_ref get_ground_refutation() const; - void get_rules_along_trace (datalog::rule_ref_vector& rules); + void get_rules_along_trace(datalog::rule_ref_vector &rules); - void collect_statistics(statistics& st) const; + void collect_statistics(statistics &st) const; void reset_statistics(); void reset(); - std::ostream& display(std::ostream& out) const; - void display_certificate(std::ostream& out) const; + std::ostream &display(std::ostream &out) const; + void display_certificate(std::ostream &out) const; - pob& get_root() const {return m_pob_queue.get_root();} - void set_query(func_decl* q) {m_query_pred = q;} - void set_unsat() {m_last_result = l_false;} - void set_model_converter(model_converter_ref& mc) {m_mc = mc;} + pob &get_root() const { return m_pob_queue.get_root(); } + void set_query(func_decl *q) { m_query_pred = q; } + void set_unsat() { m_last_result = l_false; } + void set_model_converter(model_converter_ref &mc) { m_mc = mc; } model_converter_ref get_model_converter() { return m_mc; } - void set_proof_converter(proof_converter_ref& pc) { m_pc = pc; } - scoped_ptr_vector &callbacks() {return m_callbacks;} + void set_proof_converter(proof_converter_ref &pc) { m_pc = pc; } + scoped_ptr_vector &callbacks() { return m_callbacks; } - unsigned get_num_levels(func_decl* p); + unsigned get_num_levels(func_decl *p); - expr_ref get_cover_delta(int level, func_decl* p_orig, func_decl* p); - void add_cover(int level, func_decl* pred, expr* property, bool bg = false); - expr_ref get_reachable (func_decl* p); - void add_invariant (func_decl *pred, expr* property); + expr_ref get_cover_delta(int level, func_decl *p_orig, func_decl *p); + void add_cover(int level, func_decl *pred, expr *property, bool bg = false); + expr_ref get_reachable(func_decl *p); + void add_invariant(func_decl *pred, expr *property); model_ref get_model(); - proof_ref get_proof() const {return get_ground_refutation();} + proof_ref get_proof() const { return get_ground_refutation(); } - expr_ref get_constraints (unsigned lvl); - void add_constraint (expr *c, unsigned lvl); + expr_ref get_constraints(unsigned lvl); + void add_constraint(expr *c, unsigned lvl); void new_lemma_eh(pred_transformer &pt, lemma *lem); void new_pob_eh(pob *p); bool is_inductive(); + // close all parents of may pob when gas runs out + void close_all_may_parents(pob_ref node); // three different solvers with three different sets of parameters // different solvers are used for different types of queries in spacer - solver* mk_solver0() {return m_pool0->mk_solver();} - solver* mk_solver1() {return m_pool1->mk_solver();} - solver* mk_solver2() {return m_pool2->mk_solver();} + solver *mk_solver0() { return m_pool0->mk_solver(); } + solver *mk_solver1() { return m_pool1->mk_solver(); } + solver *mk_solver2() { return m_pool2->mk_solver(); } }; -inline bool pred_transformer::use_native_mbp () {return ctx.use_native_mbp ();} -} - +inline bool pred_transformer::use_native_mbp() { return ctx.use_native_mbp(); } +} // namespace spacer diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp new file mode 100644 index 000000000..476821643 --- /dev/null +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -0,0 +1,389 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_convex_closure.cpp + +Abstract: + + Compute convex closure of polyhedra + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "muz/spacer/spacer_convex_closure.h" +#include "ast/rewriter/th_rewriter.h" + +namespace { +bool is_int_matrix(const spacer::spacer_matrix &matrix) { + rational val; + for (unsigned i = 0, rows = matrix.num_rows(); i < rows; i++) { + for (unsigned j = 0, cols = matrix.num_cols(); j < cols; j++) + if (!matrix.get(i, j).is_int()) return false; + } + return true; +} + +bool is_sorted(const vector &data) { + for (unsigned i = 0; i < data.size() - 1; i++) { + if (!(data[i] >= data[i + 1])) return false; + } + return true; +} + +/// Check whether all elements of \p data are congruent modulo \p m +bool is_congruent_mod(const vector &data, const rational &m) { + SASSERT(data.size() > 0); + rational p = data[0] % m; + for (auto k : data) + if (k % m != p) return false; + return true; +} + +app *mk_bvadd(bv_util &bv, unsigned num, expr *const *args) { + if (num == 0) return nullptr; + if (num == 1) return is_app(args[0]) ? to_app(args[0]) : nullptr; + + if (num == 2) { return bv.mk_bv_add(args[0], args[1]); } + + /// XXX no mk_bv_add for n-ary bv_add + return bv.get_manager().mk_app(bv.get_fid(), OP_BADD, num, args); +} +} // namespace + +namespace spacer { + +convex_closure::convex_closure(ast_manager &_m) + : m(_m), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_implicit(true), m_dim(0), + m_data(0, 0), m_col_vars(m), m_kernel(m_data), m_alphas(m), + m_implicit_cc(m), m_explicit_cc(m) { + + m_kernel.set_plugin(mk_simplex_kernel_plugin()); +} +void convex_closure::reset(unsigned n_cols) { + m_dim = n_cols; + m_kernel.reset(); + m_data.reset(m_dim); + m_col_vars.reset(); + m_col_vars.reserve(m_dim); + m_dead_cols.reset(); + m_dead_cols.reserve(m_dim, false); + m_alphas.reset(); + m_bv_sz = 0; + m_enable_implicit = true; +} + +void convex_closure::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.global.cc", + m_st.watch.get_seconds()); + st.update("SPACER cc num dim reduction success", m_st.m_num_reductions); + st.update("SPACER cc max reduced dim", m_st.m_max_dim); + m_kernel.collect_statistics(st); +} + +// call m_kernel to reduce dimensions of m_data +// return the rank of m_data +unsigned convex_closure::reduce() { + if (m_dim <= 1) return m_dim; + + bool has_kernel = m_kernel.compute_kernel(); + if (!has_kernel) { + TRACE("cvx_dbg", + tout << "No linear dependencies between pattern vars\n";); + return m_dim; + } + + const spacer_matrix &ker = m_kernel.get_kernel(); + SASSERT(ker.num_rows() > 0); + SASSERT(ker.num_rows() <= m_dim); + SASSERT(ker.num_cols() == m_dim + 1); + // m_dim - ker.num_rows() is the number of variables that have no linear + // dependencies + + for (auto v : m_kernel.get_basic_vars()) + // XXX sometimes a constant can be basic, need to find a way to + // switch it to var + if (v < m_dead_cols.size()) m_dead_cols[v] = true; + return m_dim - ker.num_rows(); +} + +// For row \p row in m_kernel, construct the equality: +// +// row * m_col_vars = 0 +// +// In the equality, exactly one variable from m_col_vars is on the lhs +void convex_closure::kernel_row2eq(const vector &row, expr_ref &out) { + expr_ref_buffer lhs(m); + expr_ref e1(m); + + bool is_int = false; + for (unsigned i = 0, sz = row.size(); i < sz; ++i) { + rational val_i = row.get(i); + if (val_i.is_zero()) continue; + SASSERT(val_i.is_int()); + + if (i < sz - 1) { + e1 = m_col_vars.get(i); + is_int |= m_arith.is_int(e1); + mul_by_rat(e1, val_i); + } else { + e1 = mk_numeral(val_i, is_int); + } + lhs.push_back(e1); + } + + e1 = !has_bv() ? mk_add(lhs) : mk_bvadd(m_bv, lhs.size(), lhs.data()); + e1 = m.mk_eq(e1, mk_numeral(rational::zero(), is_int)); + + // revisit this simplification step, it is here only to prevent/simplify + // formula construction everywhere else + params_ref params; + params.set_bool("som", true); + params.set_bool("flat", true); + th_rewriter rw(m, params); + rw(e1, out); +} + +/// Generates linear equalities implied by m_data +/// +/// the linear equalities are m_kernel * m_col_vars = 0 (where * is matrix +/// multiplication) the new equalities are stored in m_col_vars for each row +/// [0, 1, 0, 1 , 1] in m_kernel, the equality v1 = -1*v3 + -1*1 is +/// constructed and stored at index 1 of m_col_vars +void convex_closure::kernel2fmls(expr_ref_vector &out) { + // assume kernel has been computed already + const spacer_matrix &kern = m_kernel.get_kernel(); + SASSERT(kern.num_rows() > 0); + + TRACE("cvx_dbg", kern.display(tout);); + expr_ref eq(m); + for (unsigned i = kern.num_rows(); i > 0; i--) { + auto &row = kern.get_row(i - 1); + kernel_row2eq(row, eq); + out.push_back(eq); + } +} + +expr *convex_closure::mk_add(const expr_ref_buffer &vec) { + SASSERT(!vec.empty()); + expr_ref s(m); + if (vec.size() == 1) { + return vec[0]; + } else if (vec.size() > 1) { + return m_arith.mk_add(vec.size(), vec.data()); + } + + UNREACHABLE(); + return nullptr; +} + +expr *convex_closure::mk_numeral(const rational &n, bool is_int) { + if (!has_bv()) + return m_arith.mk_numeral(n, is_int); + else + return m_bv.mk_numeral(n, m_bv_sz); +} + +/// Construct the equality ((m_alphas . m_data[*][i]) = m_col_vars[i]) +/// +/// Where . is the dot product, m_data[*][i] is +/// the ith column of m_data. Add the result to res_vec. +void convex_closure::cc_col2eq(unsigned col, expr_ref_vector &out) { + SASSERT(!has_bv()); + + expr_ref_buffer sum(m); + for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) { + expr_ref alpha(m); + auto n = m_data.get(row, col); + if (n.is_zero()) { + ; // noop + } else { + alpha = m_alphas.get(row); + if (!n.is_one()) { + alpha = m_arith.mk_mul( + m_arith.mk_numeral(n, false /* is_int */), alpha); + } + } + if (alpha) sum.push_back(alpha); + } + SASSERT(!sum.empty()); + expr_ref s(m); + s = mk_add(sum); + + expr_ref v(m); + expr *vi = m_col_vars.get(col); + v = m_arith.is_int(vi) ? m_arith.mk_to_real(vi) : vi; + out.push_back(m.mk_eq(s, v)); +} + +void convex_closure::cc2fmls(expr_ref_vector &out) { + sort_ref real_sort(m_arith.mk_real(), m); + expr_ref zero(m_arith.mk_real(rational::zero()), m); + + for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) { + if (row >= m_alphas.size()) { + m_alphas.push_back(m.mk_fresh_const("a!cc", real_sort)); + } + SASSERT(row < m_alphas.size()); + // forall j :: alpha_j >= 0 + out.push_back(m_arith.mk_ge(m_alphas.get(row), zero)); + } + + for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) { + if (m_col_vars.get(k) && !m_dead_cols[k]) cc_col2eq(k, out); + } + + //(\Sum j . m_new_vars[j]) = 1 + out.push_back(m.mk_eq( + m_arith.mk_add(m_data.num_rows(), + reinterpret_cast(m_alphas.data())), + m_arith.mk_real(rational::one()))); +} + +#define MAX_DIV_BOUND 101 +// check whether \exists m, d s.t data[i] mod m = d. Returns the largest m and +// corresponding d +// TODO: find the largest divisor, not the smallest. +// TODO: improve efficiency +bool convex_closure::infer_div_pred(const vector &data, rational &m, + rational &d) { + TRACE("cvx_dbg_verb", { + tout << "computing div constraints for "; + for (rational r : data) tout << r << " "; + tout << "\n"; + }); + SASSERT(data.size() > 1); + SASSERT(is_sorted(data)); + + m = rational(2); + + // special handling for even/odd + if (is_congruent_mod(data, m)) { + mod(data.back(), m, d); + return true; + } + + // hard cut off to save time + rational bnd(MAX_DIV_BOUND); + rational big = data.back(); + // AG: why (m < big)? Note that 'big' is the smallest element of data + for (; m < big && m < bnd; m++) { + if (is_congruent_mod(data, m)) break; + } + if (m >= big) return false; + if (m == bnd) return false; + + mod(data[0], m, d); + SASSERT(d >= rational::zero()); + + TRACE("cvx_dbg_verb", tout << "div constraint generated. cf " << m + << " and off " << d << "\n";); + return true; +} + +bool convex_closure::compute() { + scoped_watch _w_(m_st.watch); + SASSERT(is_int_matrix(m_data)); + + unsigned rank = reduce(); + // store dim var before rewrite + expr_ref var(m_col_vars.get(0), m); + if (rank < dims()) { + m_st.m_num_reductions++; + kernel2fmls(m_explicit_cc); + TRACE("cvx_dbg", tout << "Linear equalities true of the matrix " + << mk_and(m_explicit_cc) << "\n";); + } + + m_st.m_max_dim = std::max(m_st.m_max_dim, rank); + + if (rank == 0) { + // AG: Is this possible? + return false; + } else if (rank > 1) { + if (m_enable_implicit) { + TRACE("subsume", tout << "Computing syntactic convex closure\n";); + cc2fmls(m_implicit_cc); + } else { + return false; + } + return true; + } + + SASSERT(rank == 1); + cc_1dim(var, m_explicit_cc); + return true; +} + +// construct the formula result_var <= bnd or result_var >= bnd +expr *convex_closure::mk_le_ge(expr *v, rational n, bool is_le) { + if (m_arith.is_int_real(v)) { + expr *en = m_arith.mk_numeral(n, m_arith.is_int(v)); + return is_le ? m_arith.mk_le(v, en) : m_arith.mk_ge(v, en); + } else if (m_bv.is_bv(v)) { + expr *en = m_bv.mk_numeral(n, m_bv.get_bv_size(v->get_sort())); + return is_le ? m_bv.mk_ule(v, en) : m_bv.mk_ule(en, v); + } else { + UNREACHABLE(); + } + + return nullptr; +} + +void convex_closure::cc_1dim(const expr_ref &var, expr_ref_vector &out) { + + // XXX assumes that var corresponds to col 0 + + // The convex closure over one dimension is just a bound + vector data; + m_data.get_col(0, data); + auto gt_proc = [](rational const &x, rational const &y) -> bool { + return x > y; + }; + std::sort(data.begin(), data.end(), gt_proc); + + // -- compute LB <= var <= UB + expr_ref res(m); + res = var; + // upper-bound + out.push_back(mk_le_ge(res, data[0], true)); + // lower-bound + out.push_back(mk_le_ge(res, data.back(), false)); + + // -- compute divisibility constraints + rational cr, off; + // add div constraints for all variables. + for (unsigned j = 0; j < m_data.num_cols(); j++) { + auto *v = m_col_vars.get(j); + if (v && (m_arith.is_int(v) || m_bv.is_bv(v))) { + data.reset(); + m_data.get_col(j, data); + std::sort(data.begin(), data.end(), gt_proc); + if (infer_div_pred(data, cr, off)) { + out.push_back(mk_eq_mod(v, cr, off)); + } + } + } +} + +expr *convex_closure::mk_eq_mod(expr *v, rational d, rational r) { + expr *res = nullptr; + if (m_arith.is_int(v)) { + res = m.mk_eq(m_arith.mk_mod(v, m_arith.mk_int(d)), m_arith.mk_int(r)); + } else if (m_bv.is_bv(v)) { + res = m.mk_eq(m_bv.mk_bv_urem(v, m_bv.mk_numeral(d, m_bv_sz)), + m_bv.mk_numeral(r, m_bv_sz)); + } else { + UNREACHABLE(); + } + return res; +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h new file mode 100644 index 000000000..c3676ddf5 --- /dev/null +++ b/src/muz/spacer/spacer_convex_closure.h @@ -0,0 +1,187 @@ +#pragma once +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_convex_closure.h + +Abstract: + + Compute convex closure of polyhedra + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "ast/arith_decl_plugin.h" +#include "ast/ast.h" +#include "ast/ast_util.h" +#include "muz/spacer/spacer_arith_kernel.h" +#include "muz/spacer/spacer_matrix.h" +#include "muz/spacer/spacer_util.h" +#include "util/statistics.h" + +namespace spacer { + +/// Computes a convex closure of a set of points +class convex_closure { + struct stats { + unsigned m_num_reductions; + unsigned m_max_dim; + stopwatch watch; + stats() { reset(); } + void reset() { + m_num_reductions = 0; + m_max_dim = 0; + watch.reset(); + } + }; + stats m_st; + + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // size of all bit vectors in m_col_vars + unsigned m_bv_sz; + + // Enable computation of implicit syntactic convex closure + bool m_enable_implicit; + + // number of columns in \p m_data + unsigned m_dim; + + // A vector of rational valued points + spacer_matrix m_data; + + // Variables naming columns in `m_data` + // \p m_col_vars[k] is a var for column \p k + expr_ref_vector m_col_vars; + vector m_dead_cols; + + // Kernel of \p m_data + // Set at the end of computation + spacer_arith_kernel m_kernel; + + // Free variables introduced by syntactic convex closure + // These variables are always of sort Real + expr_ref_vector m_alphas; + + expr_ref_vector m_implicit_cc; + expr_ref_vector m_explicit_cc; + + /// Reduces dimension of \p m_data and returns its rank + unsigned reduce(); + + /// Constructs an equality corresponding to a given row in the kernel + /// + /// The equality is conceptually corresponds to + /// row * m_col_vars = 0 + /// where row is a row vector and m_col_vars is a column vector. + /// However, the equality is put in a form so that exactly one variable from + /// \p m_col_vars is on the LHS + void kernel_row2eq(const vector &row, expr_ref &out); + + /// Construct all linear equations implied by points in \p m_data + /// This is defined by \p m_kernel * m_col_vars = 0 + void kernel2fmls(expr_ref_vector &out); + + /// Compute syntactic convex closure of \p m_data + void cc2fmls(expr_ref_vector &out); + + /// Construct the equality ((m_alphas . m_data[*][k]) = m_col_vars[k]) + /// + /// \p m_data[*][k] is the kth column of m_data + /// The equality is added to \p out. + void cc_col2eq(unsigned k, expr_ref_vector &out); + + /// Compute one dimensional convex closure over \p var + /// + /// \p var is the dimension over which convex closure is computed + /// Result is stored in \p out + void cc_1dim(const expr_ref &var, expr_ref_vector &out); + + /// Computes div constraint implied by a set of data points + /// + /// Finds the largest numbers \p m, \p d such that \p m_data[i] mod m = d + /// Returns true if successful + bool infer_div_pred(const vector &data, rational &m, rational &d); + + /// Constructs a formula \p var ~ n , where ~ = is_le ? <= : >= + expr *mk_le_ge(expr *var, rational n, bool is_le); + + expr *mk_add(const expr_ref_buffer &vec); + expr *mk_numeral(const rational &n, bool is_int); + + /// Returns equality (v = r mod d) + expr *mk_eq_mod(expr *v, rational d, rational r); + + bool has_bv() { return m_bv_sz > 0; } + + public: + convex_closure(ast_manager &_m); + + /// Resets all data points + /// + /// n_cols is the number of dimensions of new expected data points + void reset(unsigned n_cols); + + /// Turn support for fixed sized bit-vectors of size \p sz + /// + /// Disables syntactic convex closure as a side-effect + void set_bv(unsigned sz) { + SASSERT(sz > 0); + m_bv_sz = sz; + m_enable_implicit = false; + } + + /// \brief Name dimension \p i with a variable \p v. + void set_col_var(unsigned i, expr *v) { + SASSERT(i < dims()); + SASSERT(m_col_vars.get(i) == nullptr); + m_col_vars[i] = v; + } + + /// \brief Return number of dimensions of each point + unsigned dims() const { return m_dim; } + + /// \brief Add an n-dimensional point to convex closure + void add_row(const vector &point) { + SASSERT(point.size() == dims()); + m_data.add_row(point); + }; + + bool operator()() { return this->compute(); } + bool compute(); + bool has_implicit() { return !m_implicit_cc.empty(); } + bool has_explicit() { return !m_explicit_cc.empty(); } + + /// Returns the implicit component of convex closure (if available) + /// + /// Implicit component contains constants from get_alphas() that are + /// implicitly existentially quantified + const expr_ref_vector &get_implicit() { return m_implicit_cc; } + + /// \brief Return implicit constants in implicit convex closure + const expr_ref_vector &get_alphas() const { return m_alphas; } + + /// Returns the explicit component of convex closure (if available) + /// + /// The explicit component is in term of column variables + const expr_ref_vector &get_explicit() { return m_explicit_cc; } + + /// Returns constants used to name columns + /// + /// Explicit convex closure is in terms of these variables + const expr_ref_vector &get_col_vars() { return m_col_vars; } + + void collect_statistics(statistics &st) const; + void reset_statistics() { m_st.reset(); } +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_expand_bnd_generalizer.cpp b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp new file mode 100644 index 000000000..4f80be706 --- /dev/null +++ b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp @@ -0,0 +1,229 @@ +/*++ + Copyright (c) 2020 Arie Gurfinkel + + Module Name: + + spacer_expand_bnd_generalizer.cpp + + Abstract: + + Strengthen lemmas by changing numeral constants inside arithmetic literals + + Author: + + Hari Govind V K + Arie Gurfinkel + +--*/ +#include "muz/spacer/spacer_expand_bnd_generalizer.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/expr_safe_replace.h" + +namespace { +/// Returns true if \p e is arithmetic comparison +/// +/// Returns true if \p e is of the form \p lhs op rhs, where +/// op in {<=, <, >, >=}, and rhs is a numeric value +bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, bool &is_int, + ast_manager &m) { + arith_util arith(m); + expr *e1, *e2; + if (m.is_not(e, e1)) return is_arith_comp(e1, lhs, rhs, is_int, m); + if (arith.is_le(e, lhs, e2) || arith.is_lt(e, lhs, e2) || + arith.is_ge(e, lhs, e2) || arith.is_gt(e, lhs, e2)) + return arith.is_numeral(e2, rhs, is_int); + return false; +} + +bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, ast_manager &m) { + bool is_int; + return is_arith_comp(e, lhs, rhs, is_int, m); +} +bool is_arith_comp(const expr *e, rational &rhs, ast_manager &m) { + expr *lhs; + return is_arith_comp(e, lhs, rhs, m); +} +/// If \p lit is of the form (x op v), replace v with num +/// +/// Supports arithmetic literals where op is <, <=, >, >=, or negation +bool update_bound(const expr *lit, rational num, expr_ref &res, + bool negate = false) { + SASSERT(is_app(lit)); + ast_manager &m = res.get_manager(); + expr *e1; + if (m.is_not(lit, e1)) { return update_bound(e1, num, res, !negate); } + + arith_util arith(m); + expr *lhs; + rational val; + bool is_int; + if (!is_arith_comp(lit, lhs, val, is_int, m)) return false; + + res = m.mk_app(to_app(lit)->get_decl(), lhs, arith.mk_numeral(num, is_int)); + if (negate) { m.mk_not(res); } + return true; +} + +} // namespace +namespace spacer { + +namespace collect_rationals_ns { + +/// Finds rationals in an expression +struct proc { + ast_manager &m; + arith_util m_arith; + + vector &m_res; + proc(ast_manager &a_m, vector &res) + : m(a_m), m_arith(m), m_res(res) {} + void operator()(expr *n) const {} + void operator()(app *n) { + rational val; + if (m_arith.is_numeral(n, val)) m_res.push_back(val); + } +}; +} // namespace collect_rationals_ns + +/// Extract all numerals from an expression +void collect_rationals(expr *e, vector &res, ast_manager &m) { + collect_rationals_ns::proc proc(m, res); + quick_for_each_expr(proc, e); +} + +lemma_expand_bnd_generalizer::lemma_expand_bnd_generalizer(context &ctx) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_arith(m) { + // -- collect rationals from initial condition and transition relation + for (auto &kv : ctx.get_pred_transformers()) { + collect_rationals(kv.m_value->init(), m_values, m); + collect_rationals(kv.m_value->transition(), m_values, m); + } + + // remove duplicates + std::sort(m_values.begin(), m_values.end()); + auto last = std::unique(m_values.begin(), m_values.end()); + for (size_t i = 0, sz = std::distance(last, m_values.end()); i < sz; ++i) + m_values.pop_back(); +} + +void lemma_expand_bnd_generalizer::operator()(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + if (!lemma->get_pob()->is_expand_bnd_enabled()) return; + + expr_ref_vector cube(lemma->get_cube()); + + // -- temporary stores a core + expr_ref_vector core(m); + + expr_ref lit(m), new_lit(m); + rational bnd; + // for every literal + for (unsigned i = 0, sz = cube.size(); i < sz; i++) { + lit = cube.get(i); + if (m.is_true(lit)) continue; + if (!is_arith_comp(lit, bnd, m)) continue; + + TRACE("expand_bnd", tout << "Attempting to expand " << lit << " inside " + << cube << "\n";); + + // for every value + for (rational n : m_values) { + if (!is_interesting(lit, bnd, n)) continue; + m_st.atmpts++; + TRACE("expand_bnd", tout << "Attempting to expand " << lit + << " with numeral " << n << "\n";); + + // -- update bound on lit + VERIFY(update_bound(lit, n, new_lit)); + // -- update lit to new_lit for a new candidate lemma + cube[i] = new_lit; + + core.reset(); + core.append(cube); + // -- check that candidate is inductive + if (check_inductive(lemma, core)) { + expr_fast_mark1 in_core; + for (auto *e : core) in_core.mark(e); + for (unsigned i = 0, sz = cube.size(); i < sz; ++i) { + if (!in_core.is_marked(cube.get(i))) cube[i] = m.mk_true(); + } + // move to next literal if the current has been removed + if (!in_core.is_marked(new_lit)) break; + } else { + // -- candidate not inductive, restore original lit + cube[i] = lit; + } + } + } + + // Currently, we allow for only one round of expand bound per lemma + // Mark lemma as already expanded so that it is not generalized in this way + // again + lemma->get_pob()->disable_expand_bnd_gen(); +} + +/// Check whether \p candidate is a possible generalization for \p lemma. +/// Side-effect: update \p lemma with the new candidate +bool lemma_expand_bnd_generalizer::check_inductive(lemma_ref &lemma, + expr_ref_vector &candidate) { + TRACE("expand_bnd_verb", + tout << "Attempting to update lemma with " << candidate << "\n";); + + unsigned uses_level = 0; + auto &pt = lemma->get_pob()->pt(); + bool res = pt.check_inductive(lemma->level(), candidate, uses_level, + lemma->weakness()); + if (res) { + m_st.success++; + lemma->update_cube(lemma->get_pob(), candidate); + lemma->set_level(uses_level); + TRACE("expand_bnd", tout << "expand_bnd succeeded with " + << mk_and(candidate) << " at level " + << uses_level << "\n";); + } + return res; +} + +/// Check whether lit ==> lit[val |--> n] (barring special cases). That is, +/// whether \p lit becomes weaker if \p val is replaced with \p n +/// +/// \p lit has to be of the form t <= v where v is a numeral. +/// Special cases: +/// In the trivial case in which \p val == \p n, return false. +/// if lit is an equality or the negation of an equality, return true. +bool lemma_expand_bnd_generalizer::is_interesting(const expr *lit, rational val, + rational new_val) { + SASSERT(lit); + // the only case in which negation and non negation agree + if (val == new_val) return false; + + if (m.is_eq(lit)) return true; + + // negation is the actual negation modulo val == n + expr *e1; + if (m.is_not(lit, e1)) { + return m.is_eq(lit) || !is_interesting(e1, val, new_val); + } + + SASSERT(val != new_val); + SASSERT(is_app(lit)); + + if (to_app(lit)->get_family_id() != m_arith.get_family_id()) return false; + switch (to_app(lit)->get_decl_kind()) { + case OP_LE: + case OP_LT: + return new_val > val; + case OP_GT: + case OP_GE: + return new_val < val; + default: + return false; + } +} + +void lemma_expand_bnd_generalizer::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.expand", m_st.watch.get_seconds()); + st.update("SPACER expand_bnd attmpts", m_st.atmpts); + st.update("SPACER expand_bnd success", m_st.success); +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_expand_bnd_generalizer.h b/src/muz/spacer/spacer_expand_bnd_generalizer.h new file mode 100644 index 000000000..91ba3b417 --- /dev/null +++ b/src/muz/spacer/spacer_expand_bnd_generalizer.h @@ -0,0 +1,68 @@ +#pragma once +/*++ + Copyright (c) 2020 Arie Gurfinkel + + Module Name: + + spacer_expand_bnd_generalizer.h + + Abstract: + + Strengthen lemmas by changing numeral constants inside arithmetic literals + + Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "muz/spacer/spacer_context.h" + +namespace spacer { + +class lemma_expand_bnd_generalizer : public lemma_generalizer { + struct stats { + unsigned atmpts; + unsigned success; + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + atmpts = 0; + success = 0; + } + }; + stats m_st; + ast_manager &m; + arith_util m_arith; + + /// A set of numeral values that can be used to expand bound + vector m_values; + + public: + lemma_expand_bnd_generalizer(context &ctx); + ~lemma_expand_bnd_generalizer() override {} + + void operator()(lemma_ref &lemma) override; + + void collect_statistics(statistics &st) const override; + void reset_statistics() override { m_st.reset(); } + + private: + + /// Check whether lit ==> lit[val |--> n] (barring special cases). That is, + /// whether \p lit becomes weaker if \p val is replaced with \p n + /// + /// \p lit has to be of the form t <= v where v is a numeral. + /// Special cases: + /// In the trivial case in which \p val == \p n, return false. + /// if lit is an equality or the negation of an equality, return true. + bool is_interesting(const expr *lit, rational val, rational n); + + /// check whether \p conj is a possible generalization for \p lemma. + /// update \p lemma if it is. + bool check_inductive(lemma_ref &lemma, expr_ref_vector &candiate); +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index ae1e53259..fb26c756c 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -21,6 +21,7 @@ Revision History: #include "ast/arith_decl_plugin.h" #include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_expand_bnd_generalizer.h" namespace spacer { @@ -174,5 +175,10 @@ class limit_num_generalizer : public lemma_generalizer { void collect_statistics(statistics &st) const override; void reset_statistics() override { m_st.reset(); } }; -} // namespace spacer +lemma_generalizer * +alloc_lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible = false, + bool enable_literal_weakening = true); + +} // namespace spacer diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp new file mode 100644 index 000000000..8c0d7aa6f --- /dev/null +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -0,0 +1,796 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_global_generalizer.cpp + +Abstract: + + Global Guidance for Spacer + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include "muz/spacer/spacer_global_generalizer.h" +#include "ast/arith_decl_plugin.h" +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "muz/spacer/spacer_cluster.h" +#include "muz/spacer/spacer_concretize.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_matrix.h" +#include "muz/spacer/spacer_util.h" +#include "smt/smt_solver.h" + +using namespace spacer; + +namespace { + +// LOCAL HELPER FUNCTIONS IN ANONYMOUS NAMESPACE + +class to_real_stripper { + ast_manager &m; + arith_util m_arith; + + public: + to_real_stripper(ast_manager &_m) : m(_m), m_arith(m) {} + bool operator()(expr_ref &e, unsigned depth = 8) { + rational num; + if (m_arith.is_int(e)) return true; + if (depth == 0) return false; + if (!is_app(e)) return false; + + if (m_arith.is_to_real(e)) { + // strip to_real() + e = to_app(e)->get_arg(0); + return true; + } else if (m_arith.is_numeral(e, num)) { + // convert number to an integer + if (denominator(num).is_one()) { + e = m_arith.mk_int(num); + return true; + } else { + return false; + } + } + + app *e_app = to_app(e); + expr_ref_buffer args(m); + expr_ref kid(m); + bool dirty = false; + for (unsigned i = 0, sz = e_app->get_num_args(); i < sz; ++i) { + auto *arg = e_app->get_arg(i); + kid = arg; + if (this->operator()(kid, depth - 1)) { + dirty |= (kid.get() != arg); + args.push_back(std::move(kid)); + } else { + return false; + } + } + + if (dirty) + e = m.mk_app(e_app->get_family_id(), e_app->get_decl_kind(), + args.size(), args.data()); + + return true; + } + + bool operator()(expr_ref_vector &vec, unsigned depth = 8) { + bool res = true; + expr_ref e(m); + for (unsigned i = 0, sz = vec.size(); res && i < sz; ++i) { + e = vec.get(i); + res = this->operator()(e, depth); + if (res) { vec[i] = e; } + } + return res; + } +}; + +// Check whether \p sub contains a mapping to a bv_numeral. +// return bv_size of the bv_numeral in the first such mapping. +bool contains_bv(ast_manager &m, const substitution &sub, unsigned &sz) { + bv_util m_bv(m); + std::pair v; + expr_offset r; + rational num; + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { + sub.get_binding(j, v, r); + if (m_bv.is_numeral(r.get_expr(), num, sz)) return true; + } + return false; +} + +// Check whether 1) all expressions in the range of \p sub are bv_numerals 2) +// all bv_numerals in range are of size sz +bool all_same_sz(ast_manager &m, const substitution &sub, unsigned sz) { + bv_util m_bv(m); + std::pair v; + expr_offset r; + rational num; + unsigned n_sz; + for (unsigned j = 0; j < sub.get_num_bindings(); j++) { + sub.get_binding(j, v, r); + if (!m_bv.is_numeral(r.get_expr(), num, n_sz) || n_sz != sz) + return false; + } + return true; +} + +} // namespace + +namespace spacer { +lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool ground_pob) + : m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), m_col_names(m), + m_ground_pob(ground_pob) { + scoped_ptr factory( + mk_smt_strategic_solver_factory(symbol::null)); + m_solver = (*factory)(m, params_ref::get_empty(), false, true, false, + symbol::null); +} + +app *lemma_global_generalizer::subsumer::mk_fresh_tag() { + if (m_used_tags == m_tags.size()) { + auto *bool_sort = m.mk_bool_sort(); + // -- create 4 new tags + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + } + + return m_tags.get(m_used_tags++); +} + +lemma_global_generalizer::lemma_global_generalizer(context &ctx) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), + m_subsumer(m, ctx.use_ground_pob()), m_do_subsume(ctx.do_subsume()) {} + +void lemma_global_generalizer::operator()(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + generalize(lemma); +} + +void lemma_global_generalizer::subsumer::mk_col_names(const lemma_cluster &lc) { + + expr_offset r; + std::pair v; + + auto &lemmas = lc.get_lemmas(); + SASSERT(!lemmas.empty()); + const substitution &sub = lemmas.get(0).get_sub(); + + m_col_names.reserve(sub.get_num_bindings()); + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { + // get var id (sub is in reverse order) + sub.get_binding(sz - 1 - j, v, r); + auto *sort = r.get_expr()->get_sort(); + + if (!m_col_names.get(j) || m_col_names.get(j)->get_sort() != sort) { + // create a fresh skolem constant for the jth variable + // reuse variables if they are already here and have matching sort + m_col_names[j] = m.mk_fresh_const("mrg_cvx!!", sort); + } + } + + // -- lcm corresponds to a column, reset them since names have potentially + // changed + // -- this is a just-in-case + m_col_lcm.reset(); +} + +// Populate m_cvx_cls by 1) collecting all substitutions in the cluster \p lc +// 2) normalizing them to integer numerals +void lemma_global_generalizer::subsumer::setup_cvx_closure( + convex_closure &cc, const lemma_cluster &lc) { + expr_offset r; + std::pair v; + + mk_col_names(lc); + const lemma_info_vector &lemmas = lc.get_lemmas(); + + m_col_lcm.reset(); + + unsigned n_vars = 0; + rational num; + bool is_first = true; + for (const auto &lemma : lemmas) { + const substitution &sub = lemma.get_sub(); + if (is_first) { + n_vars = sub.get_num_bindings(); + m_col_lcm.reserve(n_vars, rational::one()); + is_first = false; + } + + for (unsigned j = 0; j < n_vars; j++) { + sub.get_binding(n_vars - 1 - j, v, r); + if (is_numeral(r.get_expr(), num)) { + m_col_lcm[j] = lcm(m_col_lcm.get(j), abs(denominator(num))); + } + } + } + + cc.reset(n_vars); + + unsigned bv_width; + if (contains_bv(m, lc.get_lemmas()[0].get_sub(), bv_width)) { + cc.set_bv(bv_width); + } + + for (unsigned j = 0; j < n_vars; ++j) + cc.set_col_var(j, mk_rat_mul(m_col_lcm.get(j), m_col_names.get(j))); + + vector row; + for (const auto &lemma : lemmas) { + row.reset(); + + const substitution &sub = lemma.get_sub(); + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { + sub.get_binding(sz - 1 - j, v, r); + VERIFY(is_numeral(r.get_expr(), num)); + row.push_back(m_col_lcm.get(j) * num); + } + + // -- add normalized row to convex closure + cc.add_row(row); + } +} + +/// Find a representative for \p c +// TODO: replace with a symbolic representative +expr *lemma_global_generalizer::subsumer::find_repr(const model_ref &mdl, + const app *c) { + return mdl->get_const_interp(c->get_decl()); +} + +/// Skolemize implicitly existentially quantified constants +/// +/// Constants in \p m_dim_frsh_cnsts are existentially quantified in \p f. They +/// are replaced by specific skolem constants. The \p out vector is populated +/// with corresponding instantiations. Currently, instantiations are values +/// chosen from the model +void lemma_global_generalizer::subsumer::skolemize_for_quic3( + expr_ref &f, const model_ref &mdl, app_ref_vector &out) { + unsigned idx = out.size(); + app_ref sk(m); + expr_ref eval(m); + expr_safe_replace sub(m); + + expr_ref_vector f_cnsts(m); + spacer::collect_uninterp_consts(f, f_cnsts); + + expr_fast_mark2 marks; + for (auto *c : f_cnsts) { marks.mark(c); } + + for (unsigned i = 0, sz = m_col_names.size(); i < sz; i++) { + app *c = m_col_names.get(i); + if (!marks.is_marked(c)) continue; + + SASSERT(m_arith.is_int(c)); + // Make skolem constants for ground pob + sk = mk_zk_const(m, i + idx, c->get_sort()); + eval = find_repr(mdl, c); + SASSERT(is_app(eval)); + out.push_back(to_app(eval)); + sub.insert(c, sk); + } + sub(f.get(), f); + TRACE("subsume", tout << "skolemized into " << f << "\n";); + m_col_names.reset(); +} + +bool lemma_global_generalizer::subsumer::find_model( + const expr_ref_vector &cc, const expr_ref_vector &alphas, expr *bg, + model_ref &out_model) { + + // push because we re-use the solver + solver::scoped_push _sp(*m_solver); + if (bg) m_solver->assert_expr(bg); + + // -- assert syntactic convex closure constraints + m_solver->assert_expr(cc); + + // if there are alphas, we have syntactic convex closure + if (!alphas.empty()) { + SASSERT(alphas.size() >= 2); + + // try to get an interior point in convex closure that also satisfies bg + { + // push because this might be unsat + solver::scoped_push _sp2(*m_solver); + expr_ref zero(m_arith.mk_real(0), m); + + for (auto *alpha : alphas) { + m_solver->assert_expr(m_arith.mk_gt(alpha, zero)); + } + + auto res = m_solver->check_sat(); + if (res == l_true) { + m_solver->get_model(out_model); + return true; + } + } + } + + // failed, try to get any point in convex closure + auto res = m_solver->check_sat(); + + if (res == l_true) { + m_solver->get_model(out_model); + return true; + } + + UNREACHABLE(); + + // something went wrong and there is no model, even though one was expected + return false; +} + +/// Returns false if subsumption is not supported for \p lc +bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) { + // check whether all substitutions are to bv_numerals + unsigned sz = 0; + bool bv_clus = contains_bv(m, lc.get_lemmas()[0].get_sub(), sz); + // If there are no BV numerals, cases are handled. + // TODO: put restriction on Arrays, non linear arithmetic etc + if (!bv_clus) return true; + if (!all_same_sz(m, lc.get_lemmas()[0].get_sub(), sz)) { + TRACE("subsume", + tout << "cannot compute cvx cls of different size variables\n";); + return false; + } + return true; +} + +void lemma_global_generalizer::subsumer::reset() { + m_used_tags = 0; + m_col_lcm.reset(); +} + +bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, + expr_ref_vector &new_post, + app_ref_vector &bindings) { + if (!is_handled(lc)) return false; + + convex_closure cvx_closure(m); + + reset(); + setup_cvx_closure(cvx_closure, lc); + + // compute convex closure + if (!cvx_closure.compute()) { return false; } + bool is_syntactic = cvx_closure.has_implicit(); + if (is_syntactic) { m_st.m_num_syn_cls++; } + + CTRACE("subsume_verb", is_syntactic, + tout << "Convex closure introduced new variables. Implicit part of " + "closure is: " + << mk_and(cvx_closure.get_implicit()) << "\n";); + + expr_ref grounded(m); + ground_free_vars(lc.get_pattern(), grounded); + + expr_ref_vector vec(m); + auto &implicit_cc = cvx_closure.get_implicit(); + auto &explicit_cc = cvx_closure.get_explicit(); + vec.append(implicit_cc.size(), implicit_cc.data()); + vec.append(explicit_cc.size(), explicit_cc.data()); + + // get a model for mbp + model_ref mdl; + auto &alphas = cvx_closure.get_alphas(); + find_model(vec, alphas, grounded, mdl); + + app_ref_vector vars(m); + expr_ref conj(m); + vec.reset(); + + // eliminate real-valued alphas from syntactic convex closure + if (!implicit_cc.empty()) { + vec.append(implicit_cc.size(), implicit_cc.data()); + conj = mk_and(vec); + vars.append(alphas.size(), + reinterpret_cast(alphas.data())); + qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob); + + // mbp failed, not expected, bail out + if (!vars.empty()) return false; + } + + // vec = [implicit_cc] + // store full cc, this is what we want to over-approximate explicitly + vec.append(explicit_cc.size(), explicit_cc.data()); + flatten_and(grounded, vec); + // vec = [implicit_cc(alpha_j, v_i), explicit_cc(v_i), phi(v_i)] + expr_ref full_cc(mk_and(vec), m); + + vec.reset(); + if (conj) { + // if explicit version of implicit cc was successfully computed + // conj is it, but need to ensure it has no to_real() + to_real_stripper stripper(m); + flatten_and(conj, vec); + stripper(vec); + } + vec.append(explicit_cc.size(), explicit_cc.data()); + + flatten_and(grounded, vec); + // here vec is [cc(v_i), phi(v_i)], and we need to eliminate v_i from it + + vars.reset(); + vars.append(m_col_names.size(), + reinterpret_cast(m_col_names.data())); + conj = mk_and(vec); + qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob); + + // failed + if (!vars.empty()) return false; + + // at the end, new_post must over-approximate the implicit convex closure + flatten_and(conj, new_post); + return over_approximate(new_post, full_cc); +} + +/// Find a weakening of \p a such that \p b ==> a +/// +/// Returns true on success and sets \p a to the result +bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a, + const expr_ref b) { + + // B && !(A1 && A2 && A3) is encoded as + // B && ((tag1 && !A1) || (tag2 && !A2) || (tag3 && !A3)) + // iterate and check tags + expr_ref_vector tags(m), tagged_a(m); + std::string tag_prefix = "o"; + for (auto *lit : a) { + tags.push_back(mk_fresh_tag()); + tagged_a.push_back(m.mk_implies(tags.back(), lit)); + } + + TRACE("subsume_verb", tout << "weakening " << mk_and(a) + << " to over approximate " << b << "\n";); + solver::scoped_push _sp(*m_solver); + m_solver->assert_expr(b); + m_solver->assert_expr(push_not(mk_and(tagged_a))); + + while (true) { + lbool res = m_solver->check_sat(tags.size(), tags.data()); + if (res == l_false) { + break; + } else if (res == l_undef) { + break; + } + + // flip tags for all satisfied literals of !A + model_ref mdl; + m_solver->get_model(mdl); + + for (unsigned i = 0, sz = a.size(); i < sz; ++i) { + if (!m.is_not(tags.get(i)) && mdl->is_false(a.get(i))) { + tags[i] = m.mk_not(tags.get(i)); + } + } + } + + expr_ref_buffer res(m); + // remove all expressions whose tags are false + for (unsigned i = 0, sz = tags.size(); i < sz; i++) { + if (!m.is_not(tags.get(i))) { res.push_back(a.get(i)); } + } + a.reset(); + a.append(res.size(), res.data()); + + if (a.empty()) { + // could not find an over approximation + TRACE("subsume", + tout << "mbp did not over-approximate convex closure\n";); + m_st.m_num_no_ovr_approx++; + return false; + } + + TRACE("subsume", + tout << "over approximate produced " << mk_and(a) << "\n";); + return true; +} + +/// Attempt to set a conjecture on pob \p n. +/// +/// Done by dropping literal \p lit from +/// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for +/// the conjecture pob returns true if conjecture is set +bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, + const expr_ref &lit, unsigned lvl, + unsigned gas) { + arith_util arith(m); + expr_ref_vector fml_vec(m); + expr_ref n_post(n->post(), m); + normalize(n_post, n_post, false, false); + // normalize_order(n_post, n_post); + fml_vec.push_back(n_post); + flatten_and(fml_vec); + + expr_ref_vector conj(m); + bool is_filtered = filter_out_lit(fml_vec, lit, conj); + expr *e1 = nullptr, *e2 = nullptr; + if (!is_filtered && + (arith.is_le(lit, e1, e2) || arith.is_ge(lit, e1, e2))) { + + // if lit is '<=' or '>=', try matching '==' + is_filtered = + filter_out_lit(fml_vec, expr_ref(m.mk_eq(e1, e2), m), conj); + } + + if (!is_filtered) { + // -- try using the corresponding lemma instead + conj.reset(); + n_post = mk_and(lemma->get_cube()); + normalize_order(n_post, n_post); + fml_vec.reset(); + fml_vec.push_back(n_post); + flatten_and(fml_vec); + is_filtered = filter_out_lit(fml_vec, lit, conj); + } + + SASSERT(0 < gas && gas < UINT_MAX); + if (conj.empty()) { + // If the pob cannot be abstracted, stop using generalization on + // it + TRACE("global", tout << "stop local generalization on pob " << n_post + << " id is " << n_post->get_id() << "\n";); + n->disable_local_gen(); + return false; + } else if (!is_filtered) { + // The literal to be abstracted is not in the pob + TRACE("global", tout << "Conjecture failed:\n" + << lit << "\n" + << n_post << "\n" + << "conj:" << conj << "\n";); + n->disable_local_gen(); + m_st.m_num_cant_abs++; + return false; + } + + pob *root = n->parent(); + while (root->parent()) root = root->parent(); + scoped_ptr new_pob = alloc(pob, root, n->pt(), lvl, n->depth(), false); + if (!new_pob) return false; + + new_pob->set_desired_level(n->level()); + + new_pob->set_post(mk_and(conj)); + new_pob->set_conjecture(); + + // -- register with current pob + n->set_data(new_pob.detach()); + + // -- update properties of the current pob itself + n->set_expand_bnd(); + n->set_gas(gas); + n->disable_local_gen(); + TRACE("global", tout << "set conjecture " << mk_pp(n->get_data()->post(), m) + << " at level " << n->get_data()->level() << "\n";); + return true; +} + +// Decide global guidance based on lemma +void lemma_global_generalizer::generalize(lemma_ref &lemma) { + // -- pob that the lemma blocks + pob_ref &pob = lemma->get_pob(); + // -- cluster that the lemma belongs to + lemma_cluster *cluster = pob->pt().clstr_match(lemma); + + /// Lemma does not belong to any cluster. return + if (!cluster) return; + + // if the cluster does not have enough gas, stop local generalization + // and return + if (cluster->get_gas() == 0) { + m_st.m_num_cls_ofg++; + pob->disable_local_gen(); + TRACE("global", tout << "stop local generalization on pob " + << mk_pp(pob->post(), m) << " id is " + << pob->post()->get_id() << "\n";); + return; + } + + // -- local cluster that includes the new lemma + lemma_cluster lc(*cluster); + // XXX most of the time lemma clustering happens before generalization + // XXX so `add_lemma` is likely to return false, but this does not mean + // XXX that the lemma is not new + bool is_new = lc.add_lemma(lemma, true); + (void)is_new; + + const expr_ref &pat = lc.get_pattern(); + + TRACE("global", { + tout << "Global generalization of:\n" + << mk_and(lemma->get_cube()) << "\n" + << "at lvl: " << lemma->level() << "\n" + << (is_new ? "new" : "old") << "\n" + << "Using cluster:\n" + << pat << "\n" + << "Existing lemmas in the cluster:\n"; + for (const auto &li : cluster->get_lemmas()) { + tout << mk_and(li.get_lemma()->get_cube()) + << " lvl:" << li.get_lemma()->level() << "\n"; + } + }); + + // Concretize + if (has_nonlinear_var_mul(pat, m)) { + m_st.m_num_non_lin++; + + TRACE("global", + tout << "Found non linear pattern. Marked to concretize \n";); + // not constructing the concrete pob here since we need a model for + // n->post() + pob->set_concretize_pattern(pat); + pob->set_concretize(true); + pob->set_gas(cluster->get_pob_gas()); + cluster->dec_gas(); + return; + } + + // Conjecture + expr_ref lit(m); + if (find_unique_mono_var_lit(pat, lit)) { + // Create a conjecture by dropping literal from pob. + TRACE("global", tout << "Conjecture with pattern\n" + << mk_pp(pat, m) << "\n" + << "with gas " << cluster->get_gas() << "\n";); + unsigned gas = cluster->get_pob_gas(); + unsigned lvl = lc.get_min_lvl(); + if (pob) lvl = std::min(lvl, pob->level()); + if (do_conjecture(pob, lemma, lit, lvl, gas)) { + // decrease the number of times this cluster is going to be used + // for conjecturing + cluster->dec_gas(); + return; + } else { + // -- if conjecture failed, there is nothing else to do. + // -- the pob matched pre-condition for conjecture, so it should not + // be subsumed + return; + } + } + + // if subsumption removed all the other lemmas, there is nothing to + // generalize + if (lc.get_size() < 2) return; + + if (!m_do_subsume) return; + // -- new pob that is blocked by generalized lemma + expr_ref_vector new_post(m); + // -- bindings for free variables of new_pob + // -- subsumer might introduce extra free variables + app_ref_vector bindings(lemma->get_bindings()); + + if (m_subsumer.subsume(lc, new_post, bindings)) { + class pob *root = pob->parent(); + while (root->parent()) root = root->parent(); + + unsigned new_lvl = lc.get_min_lvl(); + if (pob) new_lvl = std::min(new_lvl, pob->level()); + scoped_ptr new_pob = + alloc(class pob, root, pob->pt(), new_lvl, pob->depth(), false); + if (!new_pob) return; + + new_pob->set_desired_level(pob->level()); + new_pob->set_post(mk_and(new_post), bindings); + new_pob->set_subsume(); + pob->set_data(new_pob.detach()); + + // -- update properties of the pob itself + pob->set_gas(cluster->get_pob_gas() + 1); + pob->set_expand_bnd(); + // Stop local generalization. Perhaps not the best choice in general. + // Helped with one instance on our benchmarks + pob->disable_local_gen(); + cluster->dec_gas(); + + TRACE("global", tout << "Create subsume pob at level " << new_lvl + << "\n" + << mk_and(new_post) << "\n";); + } +} + +/// Replace bound vars in \p fml with uninterpreted constants +void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, + expr_ref &out) { + SASSERT(!is_ground(pat)); + var_subst vs(m, false); + // m_col_names might be bigger since it contains previously used constants + // relying on the fact that m_col_lcm was just set. Better to compute free + // vars of pat + SASSERT(m_col_lcm.size() <= m_col_names.size()); + out = vs(pat, m_col_lcm.size(), + reinterpret_cast(m_col_names.data())); + SASSERT(is_ground(out)); +} + +pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { + expr_ref_vector new_post(m); + spacer::pob_concretizer proc(m, model, n.get_concretize_pattern()); + if (proc.apply(n.post(), new_post)) { + pob *new_pob = n.pt().mk_pob(n.parent(), n.level(), n.depth(), + mk_and(new_post), n.get_binding()); + + TRACE("concretize", tout << "pob:\n" + << mk_pp(n.post(), m) + << " is concretized into:\n" + << mk_pp(new_pob->post(), m) << "\n";); + return new_pob; + } + return nullptr; +} + +pob *lemma_global_generalizer::mk_subsume_pob(pob &n) { + if (!(n.get_gas() >= 0 && n.has_data() && n.get_data()->is_subsume())) + return nullptr; + + pob *data = n.get_data(); + + pob *f = n.pt().find_pob(data->parent(), data->post()); + if (f && (f->is_in_queue() || f->is_closed())) { + n.reset_data(); + return nullptr; + } + + TRACE("global", tout << "mk_subsume_pob at level " << data->level() + << " with post state:\n" + << mk_pp(data->post(), m) << "\n";); + f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), + data->post(), n.get_binding()); + f->set_subsume(); + f->inherit(*data); + + n.reset_data(); + return f; +} + +pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) { + if (!(n.has_data() && n.get_data()->is_conjecture() && n.get_gas() > 0)) + return nullptr; + + pob *data = n.get_data(); + pob *f = n.pt().find_pob(data->parent(), data->post()); + if (f && (f->is_in_queue() || f->is_closed())) { + n.reset_data(); + return nullptr; + } + + f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), + data->post(), {m}); + + // inherit all metadata from new_pob + f->inherit(*data); + + n.reset_data(); + return f; +} + +void lemma_global_generalizer::subsumer::collect_statistics( + statistics &st) const { + st.update("SPACER num no over approximate", m_st.m_num_no_ovr_approx); + st.update("SPACER num sync cvx cls", m_st.m_num_syn_cls); + st.update("SPACER num mbp failed", m_st.m_num_mbp_failed); + // m_cvx_closure.collect_statistics(st); +} + +void lemma_global_generalizer::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.global", m_st.watch.get_seconds()); + st.update("SPACER cluster out of gas", m_st.m_num_cls_ofg); + st.update("SPACER num non lin", m_st.m_num_non_lin); + st.update("SPACER num cant abstract", m_st.m_num_cant_abs); +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h new file mode 100644 index 000000000..00b85ecd5 --- /dev/null +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -0,0 +1,176 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_global_generalizer.h + +Abstract: + + Global Guidance for Spacer + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_convex_closure.h" + +namespace spacer { + +/// Global guided generalization +/// +/// See Hari Govind et al. Global Guidance for Local Generalization in Model +/// Checking. CAV 2020 +class lemma_global_generalizer : public lemma_generalizer { + /// Subsumption strategy + class subsumer { + struct stats { + unsigned m_num_syn_cls; + unsigned m_num_mbp_failed; + unsigned m_num_no_ovr_approx; + + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + m_num_syn_cls = 0; + m_num_mbp_failed = 0; + m_num_no_ovr_approx = 0; + } + }; + stats m_st; + + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // boolean variables used as local tags + app_ref_vector m_tags; + // number of tags currently used + unsigned m_used_tags; + + // save fresh constants for mbp + app_ref_vector m_col_names; + vector m_col_lcm; + + // create pob without free vars + bool m_ground_pob; + + // Local solver to get model for computing mbp and to check whether + // cvx_cls ==> mbp + ref m_solver; + + /// Return a fresh boolean variable + app *mk_fresh_tag(); + + void reset(); + + /// Returns false if subsumption is not supported for given cluster + bool is_handled(const lemma_cluster &lc); + + /// Find a representative for \p c + expr *find_repr(const model_ref &mdl, const app *c); + + /// Skolemize m_dim_frsh_cnsts in \p f + /// + /// \p cnsts is appended with ground terms from \p mdl + void skolemize_for_quic3(expr_ref &f, const model_ref &mdl, + app_ref_vector &cnsts); + + /// Create new vars to compute convex cls + void mk_col_names(const lemma_cluster &lc); + + void setup_cvx_closure(convex_closure &cc, const lemma_cluster &lc); + + /// Make \p fml ground using m_dim_frsh_cnsts. Store result in \p out + void ground_free_vars(expr *fml, expr_ref &out); + + /// Weaken \p a such that (and a) overapproximates \p b + bool over_approximate(expr_ref_vector &a, const expr_ref b); + + bool find_model(const expr_ref_vector &cc, + const expr_ref_vector &alphas, expr *bg, + model_ref &out_model); + + bool is_numeral(const expr *e, rational &n) { + return m_arith.is_numeral(e, n) || m_bv.is_numeral(e, n); + } + + expr *mk_rat_mul(rational n, expr *v) { + if (n.is_one()) return v; + return m_arith.mk_mul(m_arith.mk_numeral(n, m_arith.is_int(v)), v); + } + + public: + subsumer(ast_manager &m, bool ground_pob); + + void collect_statistics(statistics &st) const; + + /// Compute a cube \p res such that \neg p subsumes all the lemmas in \p + /// lc + /// + /// \p cnsts is a set of constants that can be used to make \p res + /// ground + bool subsume(const lemma_cluster &lc, expr_ref_vector &res, + app_ref_vector &cnsts); + }; + + struct stats { + unsigned m_num_cls_ofg; + unsigned m_num_syn_cls; + unsigned m_num_mbp_failed; + unsigned m_num_non_lin; + unsigned m_num_no_ovr_approx; + unsigned m_num_cant_abs; + + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + m_num_cls_ofg = 0; + m_num_non_lin = 0; + m_num_syn_cls = 0; + m_num_mbp_failed = 0; + m_num_no_ovr_approx = 0; + m_num_cant_abs = 0; + } + }; + stats m_st; + ast_manager &m; + subsumer m_subsumer; + + /// Decide global guidance based on lemma + void generalize(lemma_ref &lemma); + + /// Attempt to set a conjecture on pob \p n. + /// + /// Done by dropping literal \p lit from + /// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for + /// the conjecture pob returns true if conjecture is set + bool do_conjecture(pob_ref &n, lemma_ref &lemma, const expr_ref &lit, unsigned lvl, + unsigned gas); + + /// Enable/disable subsume rule + bool m_do_subsume; + + public: + lemma_global_generalizer(context &ctx); + ~lemma_global_generalizer() override {} + + void operator()(lemma_ref &lemma) override; + + void collect_statistics(statistics &st) const override; + void reset_statistics() override { m_st.reset(); } + + // post-actions for pobs produced during generalization + pob *mk_concretize_pob(pob &n, model_ref &model); + pob *mk_subsume_pob(pob &n); + pob *mk_conjecture_pob(pob &n); +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_ind_lemma_generalizer.cpp b/src/muz/spacer/spacer_ind_lemma_generalizer.cpp new file mode 100644 index 000000000..e139e743e --- /dev/null +++ b/src/muz/spacer/spacer_ind_lemma_generalizer.cpp @@ -0,0 +1,303 @@ +#include "ast/expr_functors.h" +#include "muz/spacer/spacer_context.h" + +using namespace spacer; + +namespace { + +class contains_array_op_proc : public i_expr_pred { + ast_manager &m; + family_id m_array_fid; + + public: + contains_array_op_proc(ast_manager &manager) + : m(manager), m_array_fid(array_util(m).get_family_id()) {} + bool operator()(expr *e) override { + return is_app(e) && to_app(e)->get_family_id() == m_array_fid; + } +}; + +class lemma_inductive_generalizer : public lemma_generalizer { + struct stats { + unsigned count; + unsigned weaken_success; + unsigned weaken_fail; + stopwatch watch; + stats() { reset(); } + void reset() { + count = 0; + weaken_success = 0; + weaken_fail = 0; + watch.reset(); + } + }; + + ast_manager &m; + expr_ref m_true; + stats m_st; + bool m_only_array_eligible; + bool m_enable_litweak; + + contains_array_op_proc m_contains_array_op; + check_pred m_contains_array_pred; + + expr_ref_vector m_pinned; + lemma *m_lemma = nullptr; + spacer::pred_transformer *m_pt = nullptr; + unsigned m_weakness = 0; + unsigned m_level = 0; + ptr_vector m_cube; + + // temporary vector + expr_ref_vector m_core; + + public: + lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible = false, + bool enable_literal_weakening = true) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), + m_true(m.mk_true(), m), m_only_array_eligible(only_array_eligible), + m_enable_litweak(enable_literal_weakening), m_contains_array_op(m), + m_contains_array_pred(m_contains_array_op, m), + + m_pinned(m), m_core(m) {} + + private: + // -- true if literal \p lit is eligible to be generalized + bool is_eligible(expr *lit) { + return !m_only_array_eligible || has_arrays(lit); + } + + bool has_arrays(expr *lit) { return m_contains_array_op(lit); } + + void reset() { + m_cube.reset(); + m_weakness = 0; + m_level = 0; + m_pt = nullptr; + m_pinned.reset(); + m_core.reset(); + } + + void setup(lemma_ref &lemma) { + // check that we start in uninitialized state + SASSERT(m_pt == nullptr); + m_lemma = lemma.get(); + m_pt = &lemma->get_pob()->pt(); + m_weakness = lemma->weakness(); + m_level = lemma->level(); + auto &cube = lemma->get_cube(); + m_cube.reset(); + for (auto *lit : cube) { m_cube.push_back(lit); } + } + + // loads current generalization from m_cube to m_core + void load_cube_to_core() { + m_core.reset(); + for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true) continue; + m_core.push_back(lit); + } + } + + // returns true if m_cube is inductive + bool is_cube_inductive() { + load_cube_to_core(); + if (m_core.empty()) return false; + + unsigned used_level; + if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) { + m_level = std::max(m_level, used_level); + return true; + } + return false; + } + + // intersect m_cube with m_core + unsigned update_cube_by_core(unsigned from = 0) { + // generalize away all literals in m_cube that are not in m_core + // do not assume anything about order of literals in m_core + + unsigned success = 0; + // mark core + ast_fast_mark2 marked_core; + for (auto *v : m_core) { marked_core.mark(v); } + + // replace unmarked literals by m_true in m_cube + for (unsigned i = from, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true) continue; + if (!marked_core.is_marked(lit)) { + m_cube[i] = m_true; + success++; + } + } + return success; + } + // generalizes m_core and removes from m_cube all generalized literals + unsigned generalize_core(unsigned from = 0) { + unsigned success = 0; + unsigned used_level; + + // -- while it is possible that a single literal can be generalized to + // false, + // -- it is fairly unlikely. Thus, we give up generalizing in this case. + if (m_core.empty()) return 0; + + // -- check whether candidate in m_core is inductive + if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) { + success += update_cube_by_core(from); + // update m_level to the largest level at which the the current + // candidate in m_cube is inductive + m_level = std::max(m_level, used_level); + } + + return success; + } + + // generalizes (i.e., drops) a specific literal of m_cube + unsigned generalize1(unsigned lit_idx) { + + if (!is_eligible(m_cube.get(lit_idx))) return 0; + + // -- populate m_core with all literals except the one being generalized + m_core.reset(); + for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true || i == lit_idx) continue; + m_core.push_back(lit); + } + + return generalize_core(lit_idx); + } + + // generalizes all literals of m_cube in a given range + unsigned generalize_range(unsigned from, unsigned to) { + unsigned success = 0; + for (unsigned i = from; i < to; ++i) { success += generalize1(i); } + return success; + } + + // weakens a given literal of m_cube + // weakening replaces a literal by a weaker literal(s) + // for example, x=y might get weakened into one of x<=y or y<=x + unsigned weaken1(unsigned lit_idx) { + if (!is_eligible(m_cube.get(lit_idx))) return 0; + if (m_cube.get(lit_idx) == m_true) return 0; + + unsigned success = 0; + unsigned cube_sz = m_cube.size(); + + // -- save literal to be generalized, and replace it by true + expr *saved_lit = m_cube.get(lit_idx); + m_cube[lit_idx] = m_true; + + // -- add new weaker literals to end of m_cube and attempt to generalize + expr_ref_vector weakening(m); + weakening.push_back(saved_lit); + expand_literals(m, weakening); + if (weakening.get(0) != saved_lit) { + for (auto *lit : weakening) { + m_cube.push_back(lit); + m_pinned.push_back(lit); + } + + if (m_cube.size() - cube_sz >= 2) { + // normal case: generalize new weakening + success += generalize_range(cube_sz, m_cube.size()); + } else { + // special case -- weaken literal by another literal, check that + // cube is still inductive + success += (is_cube_inductive() ? 1 : 0); + } + } + + // -- failed to generalize, restore removed literal and m_cube + if (success == 0) { + m_cube[lit_idx] = saved_lit; + m_cube.shrink(cube_sz); + m_st.weaken_fail++; + } else { + m_st.weaken_success++; + } + + return success; + } + + // weakens literals of m_cube in a given range + unsigned weaken_range(unsigned from, unsigned to) { + unsigned success = 0; + for (unsigned i = from; i < to; ++i) { success += weaken1(i); } + return success; + } + + public: + // entry point for generalization + void operator()(lemma_ref &lemma) override { + if (lemma->get_cube().empty()) return; + + m_st.count++; + scoped_watch _w_(m_st.watch); + + setup(lemma); + + unsigned num_gens = 0; + + // -- first round -- generalize by dropping literals + num_gens += generalize_range(0, m_cube.size()); + + // -- if weakening is enabled, start next round + if (m_enable_litweak) { + unsigned cube_sz = m_cube.size(); + // -- second round -- weaken literals that cannot be dropped + num_gens += weaken_range(0, cube_sz); + + // -- third round -- weaken literals produced in prev round + if (cube_sz < m_cube.size()) + num_gens += weaken_range(cube_sz, m_cube.size()); + } + + // if there is at least one generalization, update lemma + if (num_gens > 0) { + TRACE("indgen", + tout << "Generalized " << num_gens << " literals\n";); + + // reuse m_core since it is not needed for anything else + m_core.reset(); + for (auto *lit : m_cube) { + if (lit != m_true) m_core.push_back(lit); + } + + TRACE("indgen", tout << "Original: " << lemma->get_cube() << "\n" + << "Generalized: " << m_core << "\n";); + + lemma->update_cube(lemma->get_pob(), m_core); + lemma->set_level(m_level); + } + + reset(); + + return; + } + + void collect_statistics(statistics &st) const override { + st.update("time.spacer.solve.reach.gen.ind", m_st.watch.get_seconds()); + st.update("SPACER inductive gen", m_st.count); + st.update("SPACER inductive gen weaken success", m_st.weaken_success); + st.update("SPACER inductive gen weaken fail", m_st.weaken_fail); + } + void reset_statistics() override { m_st.reset(); } +}; +} // namespace + +namespace spacer { +lemma_generalizer * +alloc_lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible, + bool enable_literal_weakening) { + return alloc(lemma_inductive_generalizer, ctx, only_array_eligible, + enable_literal_weakening); +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_json.cpp b/src/muz/spacer/spacer_json.cpp deleted file mode 100644 index a99a8f298..000000000 --- a/src/muz/spacer/spacer_json.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/**++ -Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti - -Module Name: - - spacer_json.cpp - -Abstract: - - SPACER json marshalling support - -Author: - - Matteo Marescotti - -Notes: - ---*/ - -#include -#include "spacer_context.h" -#include "spacer_json.h" -#include "spacer_util.h" - -namespace spacer { - -static std::ostream &json_marshal(std::ostream &out, ast *t, ast_manager &m) { - - mk_epp pp = mk_epp(t, m); - std::ostringstream ss; - ss << pp; - out << "\""; - for (auto &c:ss.str()) { - switch (c) { - case '"': - out << "\\\""; - break; - case '\\': - out << "\\\\"; - break; - case '\b': - out << "\\b"; - break; - case '\f': - out << "\\f"; - break; - case '\n': - out << "\\n"; - break; - case '\r': - out << "\\r"; - break; - case '\t': - out << "\\t"; - break; - default: - if ('\x00' <= c && c <= '\x1f') { - out << "\\u" - << std::hex << std::setw(4) << std::setfill('0') << (int) c; - } else { - out << c; - } - } - } - out << "\""; - return out; -} - -static std::ostream &json_marshal(std::ostream &out, lemma *l) { - out << "{" - << R"("init_level":")" << l->init_level() - << R"(", "level":")" << l->level() - << R"(", "expr":)"; - json_marshal(out, l->get_expr(), l->get_ast_manager()); - out << "}"; - return out; -} - -static std::ostream &json_marshal(std::ostream &out, const lemma_ref_vector &lemmas) { - - std::ostringstream ls; - for (auto l:lemmas) { - ls << ((unsigned)ls.tellp() == 0 ? "" : ","); - json_marshal(ls, l); - } - out << "[" << ls.str() << "]"; - return out; - } - - -void json_marshaller::register_lemma(lemma *l) { - if (l->has_pob()) { - m_relations[&*l->get_pob()][l->get_pob()->depth()].push_back(l); - } -} - -void json_marshaller::register_pob(pob *p) { - m_relations[p]; -} - -void json_marshaller::marshal_lemmas_old(std::ostream &out) const { - unsigned pob_id = 0; - for (auto &pob_map:m_relations) { - std::ostringstream pob_lemmas; - for (auto &depth_lemmas : pob_map.second) { - pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") - << "\"" << depth_lemmas.first << "\":"; - json_marshal(pob_lemmas, depth_lemmas.second); - } - if (pob_lemmas.tellp()) { - out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); - out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; - } - pob_id++; - } -} -void json_marshaller::marshal_lemmas_new(std::ostream &out) const { - unsigned pob_id = 0; - for (auto &pob_map:m_relations) { - std::ostringstream pob_lemmas; - pob *n = pob_map.first; - unsigned i = 0; - for (auto *l : n->lemmas()) { - pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") - << "\"" << i++ << "\":"; - lemma_ref_vector lemmas_vec; - lemmas_vec.push_back(l); - json_marshal(pob_lemmas, lemmas_vec); - } - - if (pob_lemmas.tellp()) { - out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); - out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; - } - pob_id++; - } -} - -std::ostream &json_marshaller::marshal(std::ostream &out) const { - std::ostringstream nodes; - std::ostringstream edges; - std::ostringstream lemmas; - - if (m_old_style) - marshal_lemmas_old(lemmas); - else - marshal_lemmas_new(lemmas); - - unsigned pob_id = 0; - unsigned depth = 0; - while (true) { - double root_expand_time = m_ctx->get_root().get_expand_time(depth); - bool a = false; - pob_id = 0; - for (auto &pob_map:m_relations) { - pob *n = pob_map.first; - double expand_time = n->get_expand_time(depth); - if (expand_time > 0) { - a = true; - std::ostringstream pob_expr; - json_marshal(pob_expr, n->post(), n->get_ast_manager()); - - nodes << ((unsigned)nodes.tellp() == 0 ? "" : ",\n") << - "{\"id\":\"" << depth << n << - "\",\"relative_time\":\"" << expand_time / root_expand_time << - "\",\"absolute_time\":\"" << std::setprecision(2) << expand_time << - "\",\"predicate\":\"" << n->pt().head()->get_name() << - "\",\"expr_id\":\"" << n->post()->get_id() << - "\",\"pob_id\":\"" << pob_id << - "\",\"depth\":\"" << depth << - "\",\"expr\":" << pob_expr.str() << "}"; - if (n->parent()) { - edges << ((unsigned)edges.tellp() == 0 ? "" : ",\n") << - "{\"from\":\"" << depth << n->parent() << - "\",\"to\":\"" << depth << n << "\"}"; - } - } - pob_id++; - } - if (!a) { - break; - } - depth++; - } - out << "{\n\"nodes\":[\n" << nodes.str() << "\n],\n"; - out << "\"edges\":[\n" << edges.str() << "\n],\n"; - out << "\"lemmas\":{\n" << lemmas.str() << "\n}\n}\n"; - return out; -} - -} diff --git a/src/muz/spacer/spacer_json.h b/src/muz/spacer/spacer_json.h deleted file mode 100644 index bb330cc03..000000000 --- a/src/muz/spacer/spacer_json.h +++ /dev/null @@ -1,59 +0,0 @@ -/**++ -Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti - -Module Name: - - spacer_json.h - -Abstract: - - SPACER json marshalling support - -Author: - - Matteo Marescotti - -Notes: - ---*/ - -#pragma once - -#include -#include -#include "util/ref.h" -#include "util/ref_vector.h" - -class ast; - -class ast_manager; - -namespace spacer { - -class lemma; -typedef sref_vector lemma_ref_vector; -class context; -class pob; - - -class json_marshaller { - context *m_ctx; - bool m_old_style; - std::map> m_relations; - - void marshal_lemmas_old(std::ostream &out) const; - void marshal_lemmas_new(std::ostream &out) const; -public: - json_marshaller(context *ctx, bool old_style = false) : - m_ctx(ctx), m_old_style(old_style) {} - - void register_lemma(lemma *l); - - void register_pob(pob *p); - - std::ostream &marshal(std::ostream &out) const; -}; - -} - - diff --git a/src/muz/spacer/spacer_matrix.cpp b/src/muz/spacer/spacer_matrix.cpp index ea9b3cb32..02d411950 100644 --- a/src/muz/spacer/spacer_matrix.cpp +++ b/src/muz/spacer/spacer_matrix.cpp @@ -17,143 +17,169 @@ Revision History: --*/ #include "muz/spacer/spacer_matrix.h" -namespace spacer -{ - spacer_matrix::spacer_matrix(unsigned m, unsigned n) : m_num_rows(m), m_num_cols(n) - { - for (unsigned i=0; i < m; ++i) - { - vector v; - for (unsigned j=0; j < n; ++j) - { - v.push_back(rational(0)); +namespace spacer { +spacer_matrix::spacer_matrix(unsigned m, unsigned n) + : m_num_rows(m), m_num_cols(n) { + m_matrix.reserve(m_num_rows); + for (unsigned i = 0; i < m_num_rows; ++i) { + m_matrix[i].reserve(m_num_cols, rational(0)); + } +} + +void spacer_matrix::get_col(unsigned i, vector &row) const { + SASSERT(i < m_num_cols); + row.reset(); + row.reserve(m_num_rows); + unsigned j = 0; + for (auto &v : m_matrix) { row[j++] = (v.get(i)); } + SASSERT(row.size() == m_num_rows); +} + +void spacer_matrix::add_row(const vector &row) { + SASSERT(row.size() == m_num_cols); + m_matrix.push_back(row); + m_num_rows = m_matrix.size(); +} + +unsigned spacer_matrix::perform_gaussian_elimination() { + unsigned i = 0; + unsigned j = 0; + while (i < m_matrix.size() && j < m_matrix[0].size()) { + // find maximal element in column with row index bigger or equal i + rational max = m_matrix[i][j]; + unsigned max_index = i; + + for (unsigned k = i + 1; k < m_matrix.size(); ++k) { + if (max < m_matrix[k][j]) { + max = m_matrix[k][j]; + max_index = k; } - m_matrix.push_back(v); } - } - unsigned spacer_matrix::num_rows() - { - return m_num_rows; - } - - unsigned spacer_matrix::num_cols() - { - return m_num_cols; - } - - const rational& spacer_matrix::get(unsigned int i, unsigned int j) - { - SASSERT(i < m_num_rows); - SASSERT(j < m_num_cols); - - return m_matrix[i][j]; - } - - void spacer_matrix::set(unsigned int i, unsigned int j, const rational& v) - { - SASSERT(i < m_num_rows); - SASSERT(j < m_num_cols); - - m_matrix[i][j] = v; - } - - unsigned spacer_matrix::perform_gaussian_elimination() - { - unsigned i=0; - unsigned j=0; - while(i < m_matrix.size() && j < m_matrix[0].size()) + if (max.is_zero()) // skip this column { - // find maximal element in column with row index bigger or equal i - rational max = m_matrix[i][j]; - unsigned max_index = i; + ++j; + } else { + // reorder rows if necessary + vector tmp = m_matrix[i]; + m_matrix[i] = m_matrix[max_index]; + m_matrix[max_index] = m_matrix[i]; - for (unsigned k=i+1; k < m_matrix.size(); ++k) - { - if (max < m_matrix[k][j]) - { - max = m_matrix[k][j]; - max_index = k; + // normalize row + rational pivot = m_matrix[i][j]; + if (!pivot.is_one()) { + for (unsigned k = 0; k < m_matrix[i].size(); ++k) { + m_matrix[i][k] = m_matrix[i][k] / pivot; } } - if (max.is_zero()) // skip this column - { - ++j; - } - else - { - // reorder rows if necessary - vector tmp = m_matrix[i]; - m_matrix[i] = m_matrix[max_index]; - m_matrix[max_index] = m_matrix[i]; - - // normalize row - rational pivot = m_matrix[i][j]; - if (!pivot.is_one()) - { - for (unsigned k=0; k < m_matrix[i].size(); ++k) - { - m_matrix[i][k] = m_matrix[i][k] / pivot; + // subtract row from all other rows + for (unsigned k = 1; k < m_matrix.size(); ++k) { + if (k != i) { + rational factor = m_matrix[k][j]; + for (unsigned l = 0; l < m_matrix[k].size(); ++l) { + m_matrix[k][l] = + m_matrix[k][l] - (factor * m_matrix[i][l]); } } - - // subtract row from all other rows - for (unsigned k=1; k < m_matrix.size(); ++k) - { - if (k != i) - { - rational factor = m_matrix[k][j]; - for (unsigned l=0; l < m_matrix[k].size(); ++l) - { - m_matrix[k][l] = m_matrix[k][l] - (factor * m_matrix[i][l]); - } - } - } - - ++i; - ++j; } - } - if (get_verbosity_level() >= 1) - { - SASSERT(m_matrix.size() > 0); + ++i; + ++j; } - - return i; //i points to the row after the last row which is non-zero } - void spacer_matrix::print_matrix() - { - verbose_stream() << "\nMatrix\n"; - for (const auto& row : m_matrix) - { - for (const auto& element : row) - { - verbose_stream() << element << ", "; - } - verbose_stream() << "\n"; - } - verbose_stream() << "\n"; + if (get_verbosity_level() >= 1) { SASSERT(m_matrix.size() > 0); } + + return i; // i points to the row after the last row which is non-zero +} + +std::ostream &spacer_matrix::display(std::ostream &out) const { + out << "Matrix\n"; + for (const auto &row : m_matrix) { + for (const auto &element : row) { out << element << ", "; } + out << "\n"; } - void spacer_matrix::normalize() - { - rational den = rational::one(); - for (unsigned i=0; i < m_num_rows; ++i) - { - for (unsigned j=0; j < m_num_cols; ++j) - { - den = lcm(den, denominator(m_matrix[i][j])); - } + out << "\n"; + return out; +} + +void spacer_matrix::normalize() { + rational den = rational::one(); + for (unsigned i = 0; i < m_num_rows; ++i) { + for (unsigned j = 0; j < m_num_cols; ++j) { + den = lcm(den, denominator(m_matrix[i][j])); } - for (unsigned i=0; i < m_num_rows; ++i) - { - for (unsigned j=0; j < m_num_cols; ++j) - { - m_matrix[i][j] = den * m_matrix[i][j]; - SASSERT(m_matrix[i][j].is_int()); - } + } + + for (unsigned i = 0; i < m_num_rows; ++i) { + for (unsigned j = 0; j < m_num_cols; ++j) { + m_matrix[i][j] = den * m_matrix[i][j]; + SASSERT(m_matrix[i][j].is_int()); } } } + +// attempt to guess that all rows of the matrix are linearly dependent +bool spacer_matrix::is_lin_reltd(unsigned i, unsigned j, rational &coeff1, + rational &coeff2, rational &off) const { + SASSERT(m_num_rows > 1); + coeff1 = m_matrix[0][j] - m_matrix[1][j]; + coeff2 = m_matrix[1][i] - m_matrix[0][i]; + off = (m_matrix[0][i] * m_matrix[1][j]) - (m_matrix[1][i] * m_matrix[0][j]); + + for (unsigned k = 0; k < m_num_rows; k++) { + if (((coeff1 * m_matrix[k][i]) + (coeff2 * m_matrix[k][j]) + off) != + rational::zero()) { + TRACE("cvx_dbg_verb", + tout << "Didn't work for " << m_matrix[k][i] << " and " + << m_matrix[k][j] << " with coefficients " << coeff1 + << " , " << coeff2 << " and offset " << off << "\n";); + return false; + } + } + + rational div = gcd(coeff1, gcd(coeff2, off)); + if (div == 0) return false; + coeff1 = coeff1 / div; + coeff2 = coeff2 / div; + off = off / div; + return true; +} + +bool spacer_matrix::compute_linear_deps(spacer_matrix &eq) const { + SASSERT(m_num_rows > 1); + + eq.reset(m_num_cols + 1); + + rational coeff1, coeff2, off; + vector lin_dep; + lin_dep.reserve(m_num_cols + 1); + + for (unsigned i = 0; i < m_num_cols; i++) { + for (unsigned j = i + 1; j < m_num_cols; j++) { + if (is_lin_reltd(i, j, coeff1, coeff2, off)) { + SASSERT(!(coeff1 == 0 && coeff2 == 0 && off == 0)); + lin_dep[i] = coeff1; + lin_dep[j] = coeff2; + lin_dep[m_num_cols] = off; + eq.add_row(lin_dep); + + TRACE("cvx_dbg_verb", { + tout << "Adding row "; + for (rational r : lin_dep) tout << r << " "; + tout << "\n"; + }); + // reset everything + lin_dep[i] = rational::zero(); + lin_dep[j] = rational::zero(); + lin_dep[m_num_cols] = 0; + // Found a dependency for this row, move on. + // sound because of transitivity of is_lin_reltd + break; + } + } + } + return eq.num_rows() > 0; +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_matrix.h b/src/muz/spacer/spacer_matrix.h index 334433ad7..d69b49466 100644 --- a/src/muz/spacer/spacer_matrix.h +++ b/src/muz/spacer/spacer_matrix.h @@ -22,24 +22,44 @@ Revision History: namespace spacer { - class spacer_matrix { - public: - spacer_matrix(unsigned m, unsigned n); // m rows, n columns +class spacer_matrix { + private: + unsigned m_num_rows; + unsigned m_num_cols; + vector> m_matrix; - unsigned num_rows(); - unsigned num_cols(); + bool is_lin_reltd(unsigned i, unsigned j, rational &coeff1, + rational &coeff2, rational &off) const; - const rational& get(unsigned i, unsigned j); - void set(unsigned i, unsigned j, const rational& v); + public: + spacer_matrix(unsigned m, unsigned n); // m rows, n columns - unsigned perform_gaussian_elimination(); + unsigned num_rows() const { return m_num_rows; } + unsigned num_cols() const { return m_num_cols; } - void print_matrix(); - void normalize(); - private: - unsigned m_num_rows; - unsigned m_num_cols; - vector> m_matrix; - }; -} + const rational &get(unsigned i, unsigned j) const { return m_matrix[i][j]; } + void set(unsigned i, unsigned j, const rational &v) { m_matrix[i][j] = v; } + const vector &get_row(unsigned i) const { + SASSERT(i < num_rows()); + return m_matrix.get(i); + } + + /// Returns a copy of row \p i + void get_col(unsigned i, vector &row) const; + + void add_row(const vector &row); + + void reset(unsigned n_cols) { + m_num_rows = 0; + m_num_cols = n_cols; + m_matrix.reset(); + } + + std::ostream &display(std::ostream &out) const; + void normalize(); + unsigned perform_gaussian_elimination(); + + bool compute_linear_deps(spacer_matrix &eq) const; +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_proof_utils.cpp b/src/muz/spacer/spacer_proof_utils.cpp index a3ebb028a..800a63291 100644 --- a/src/muz/spacer/spacer_proof_utils.cpp +++ b/src/muz/spacer/spacer_proof_utils.cpp @@ -284,6 +284,11 @@ namespace spacer { ptr_buffer const &parents, unsigned num_params, parameter const *params) { + if(num_params != parents.size() + 1) { + //TODO: fix bug + TRACE("spacer.fkab", tout << "UNEXPECTED INPUT TO FUNCTION. Bailing out\n";); + return proof_ref(m); + } SASSERT(num_params == parents.size() + 1 /* one param is missing */); arith_util a(m); th_rewriter rw(m); diff --git a/src/muz/spacer/spacer_sem_matcher.cpp b/src/muz/spacer/spacer_sem_matcher.cpp index f3c2af8a9..b0eeb51a3 100644 --- a/src/muz/spacer/spacer_sem_matcher.cpp +++ b/src/muz/spacer/spacer_sem_matcher.cpp @@ -84,7 +84,7 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos) top = false; if (n1->get_decl() != n2->get_decl()) { - expr *e1 = nullptr, *e2 = nullptr; + expr *e1 = nullptr, *e2 = nullptr, *e3 = nullptr, *e4 = nullptr, *e5 = nullptr; rational val1, val2; // x<=y == !(x>y) @@ -120,6 +120,26 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos) else { return false; } +#if 0 + // x >= var and !(y <= n) + // match (x, y) and (var, n+1) + if (m_arith.is_ge(n1, e1, e2) && is_var(e2) && + m.is_not(n2, e3) && m_arith.is_le(e3, e4, e5) && + m_arith.is_int(e5) && + m_arith.is_numeral(e5, val2)) { + + expr* num2 = m_arith.mk_numeral(val2 + 1, true); + m_pinned.push_back(num2); + + if (!match_var(to_var(e2), num2)) return false; + + m_todo.pop_back(); + + m_todo.push_back(expr_pair(e1, e4)); + + continue; + } +#endif } unsigned num_args1 = n1->get_num_args(); diff --git a/src/muz/spacer/spacer_sem_matcher.h b/src/muz/spacer/spacer_sem_matcher.h index f39aef307..1df4a77b5 100644 --- a/src/muz/spacer/spacer_sem_matcher.h +++ b/src/muz/spacer/spacer_sem_matcher.h @@ -38,11 +38,11 @@ class sem_matcher { substitution * m_subst; svector m_todo; - void reset(); bool match_var(var *v, expr *e); public: sem_matcher(ast_manager &man); + void reset(); /** \brief Return true if e2 is an instance of e1. diff --git a/src/muz/spacer/spacer_unsat_core_plugin.cpp b/src/muz/spacer/spacer_unsat_core_plugin.cpp index e0c5a39f5..5523326f4 100644 --- a/src/muz/spacer/spacer_unsat_core_plugin.cpp +++ b/src/muz/spacer/spacer_unsat_core_plugin.cpp @@ -396,8 +396,8 @@ namespace spacer { matrix.set(i, map[pair.second], pair.first); } } - matrix.print_matrix(); + IF_VERBOSE(10, matrix.display(verbose_stream());); // 3. normalize matrix to integer values matrix.normalize(); diff --git a/src/muz/spacer/spacer_unsat_core_plugin.h b/src/muz/spacer/spacer_unsat_core_plugin.h index 581a877cf..a7ea4c89b 100644 --- a/src/muz/spacer/spacer_unsat_core_plugin.h +++ b/src/muz/spacer/spacer_unsat_core_plugin.h @@ -31,7 +31,7 @@ namespace spacer { ast_manager& m; public: unsat_core_plugin(unsat_core_learner& learner); - virtual ~unsat_core_plugin() {}; + virtual ~unsat_core_plugin() = default; virtual void compute_partial_core(proof* step) = 0; virtual void finalize(){}; diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index d16b37972..bc9224771 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -25,986 +25,1059 @@ Notes: --*/ -#include #include +#include -#include "util/util.h" -#include "ast/ast.h" -#include "ast/occurs.h" -#include "ast/ast_pp.h" -#include "ast/scoped_proof.h" -#include "ast/for_each_expr.h" -#include "ast/rewriter/bool_rewriter.h" -#include "ast/rewriter/expr_safe_replace.h" -#include "ast/array_decl_plugin.h" #include "ast/arith_decl_plugin.h" -#include "ast/datatype_decl_plugin.h" +#include "ast/array_decl_plugin.h" +#include "ast/ast.h" +#include "ast/ast_pp.h" #include "ast/bv_decl_plugin.h" +#include "ast/datatype_decl_plugin.h" +#include "ast/for_each_expr.h" +#include "ast/occurs.h" +#include "ast/rewriter/bool_rewriter.h" +#include "ast/rewriter/expr_replacer.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/rewriter/factor_equivs.h" #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" -#include "ast/rewriter/factor_equivs.h" -#include "ast/rewriter/expr_replacer.h" +#include "ast/scoped_proof.h" +#include "util/util.h" - -#include "smt/params/smt_params.h" #include "model/model.h" #include "model/model_evaluator.h" -#include "model/model_smt2_pp.h" #include "model/model_pp.h" +#include "model/model_smt2_pp.h" +#include "smt/params/smt_params.h" #include "qe/lite/qe_lite.h" -#include "qe/qe_mbp.h" -#include "qe/mbp/mbp_term_graph.h" #include "qe/mbp/mbp_plugin.h" +#include "qe/mbp/mbp_term_graph.h" +#include "qe/qe_mbp.h" -#include "tactic/tactical.h" -#include "tactic/core/propagate_values_tactic.h" -#include "tactic/arith/propagate_ineqs_tactic.h" #include "tactic/arith/arith_bounds_tactic.h" +#include "tactic/arith/propagate_ineqs_tactic.h" +#include "tactic/core/propagate_values_tactic.h" +#include "tactic/tactical.h" #include "muz/base/dl_util.h" #include "muz/spacer/spacer_legacy_mev.h" -#include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_util.h" namespace spacer { - bool is_clause(ast_manager& m, expr* n) { - if (spacer::is_literal(m, n)) - return true; - if (m.is_or(n)) { - for (expr* arg : *to_app(n)) { - if (!spacer::is_literal(m, arg)) - return false; - return true; - } - } - return false; - } - - bool is_literal(ast_manager& m, expr* n) { - return spacer::is_atom(m, n) || (m.is_not(n) && spacer::is_atom(m, to_app(n)->get_arg(0))); - } - - bool is_atom(ast_manager& m, expr* n) { - if (is_quantifier(n) || !m.is_bool(n)) - return false; - if (is_var(n)) - return true; - SASSERT(is_app(n)); - if (to_app(n)->get_family_id() != m.get_basic_family_id()) { +bool is_clause(ast_manager &m, expr *n) { + if (spacer::is_literal(m, n)) return true; + if (m.is_or(n)) { + for (expr *arg : *to_app(n)) { + if (!spacer::is_literal(m, arg)) return false; return true; } + } + return false; +} - if ((m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || - m.is_true(n) || m.is_false(n)) - return true; +bool is_literal(ast_manager &m, expr *n) { + return spacer::is_atom(m, n) || + (m.is_not(n) && spacer::is_atom(m, to_app(n)->get_arg(0))); +} - // x=y is atomic if x and y are Bool and atomic - expr* e1, * e2; - if (m.is_eq(n, e1, e2) && spacer::is_atom(m, e1) && spacer::is_atom(m, e2)) - return true; - return false; +bool is_atom(ast_manager &m, expr *n) { + if (is_quantifier(n) || !m.is_bool(n)) return false; + if (is_var(n)) return true; + SASSERT(is_app(n)); + if (to_app(n)->get_family_id() != m.get_basic_family_id()) { return true; } + + if ((m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || m.is_true(n) || + m.is_false(n)) + return true; + + // x=y is atomic if x and y are Bool and atomic + expr *e1, *e2; + if (m.is_eq(n, e1, e2) && spacer::is_atom(m, e1) && spacer::is_atom(m, e2)) + return true; + return false; +} + +void subst_vars(ast_manager &m, app_ref_vector const &vars, model &mdl, + expr_ref &fml) { + model::scoped_model_completion _sc_(mdl, true); + expr_safe_replace sub(m); + for (app *v : vars) sub.insert(v, mdl(v)); + sub(fml); +} + +void to_mbp_benchmark(std::ostream &out, expr *fml, + const app_ref_vector &vars) { + ast_manager &m = vars.m(); + ast_pp_util pp(m); + pp.collect(fml); + pp.display_decls(out); + + out << "(define-fun mbp_benchmark_fml () Bool\n "; + out << mk_pp(fml, m) << ")\n\n"; + + out << "(push 1)\n" + << "(assert mbp_benchmark_fml)\n" + << "(check-sat)\n" + << "(mbp mbp_benchmark_fml ("; + for (auto v : vars) { out << mk_pp(v, m) << " "; } + out << "))\n" + << "(pop 1)\n" + << "(exit)\n"; +} + +void qe_project_z3(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model &mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { + params_ref p; + p.set_bool("reduce_all_selects", reduce_all_selects); + p.set_bool("dont_sub", dont_sub); + + qe::mbproj mbp(m, p); + mbp.spacer(vars, mdl, fml); +} + +/* + * eliminate simple equalities using qe_lite + * then, MBP for Booleans (substitute), reals (based on LW), ints (based on + * Cooper), and arrays + */ +void qe_project_spacer(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model &mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { + th_rewriter rw(m); + TRACE("spacer_mbp", tout << "Before projection:\n"; tout << fml << "\n"; + tout << "Vars:\n" + << vars;); + + { + // Ensure that top-level AND of fml is flat + expr_ref_vector flat(m); + flatten_and(fml, flat); + fml = mk_and(flat); } - void subst_vars(ast_manager& m, - app_ref_vector const& vars, model& mdl, expr_ref& fml) { - model::scoped_model_completion _sc_(mdl, true); - expr_safe_replace sub(m); - for (app* v : vars) sub.insert(v, mdl(v)); - sub(fml); - } + // uncomment for benchmarks + // to_mbp_benchmark(verbose_stream(), fml, vars); - void to_mbp_benchmark(std::ostream& out, expr* fml, const app_ref_vector& vars) { - ast_manager& m = vars.m(); - ast_pp_util pp(m); - pp.collect(fml); - pp.display_decls(out); + app_ref_vector arith_vars(m); + app_ref_vector array_vars(m); + array_util arr_u(m); + arith_util ari_u(m); + expr_safe_replace bool_sub(m); + expr_ref bval(m); - out << "(define-fun mbp_benchmark_fml () Bool\n "; - out << mk_pp(fml, m) << ")\n\n"; - - out << "(push 1)\n" - << "(assert mbp_benchmark_fml)\n" - << "(check-sat)\n" - << "(mbp mbp_benchmark_fml ("; - for (auto v : vars) { out << mk_pp(v, m) << " "; } - out << "))\n" - << "(pop 1)\n" - << "(exit)\n"; - } - - void qe_project_z3(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { + while (true) { params_ref p; - p.set_bool("reduce_all_selects", reduce_all_selects); - p.set_bool("dont_sub", dont_sub); + qe_lite qe(m, p, false); + qe(vars, fml); + rw(fml); - qe::mbproj mbp(m, p); - mbp.spacer(vars, mdl, fml); - } + TRACE("spacer_mbp", tout << "After qe_lite:\n"; + tout << mk_pp(fml, m) << "\n"; tout << "Vars:\n" + << vars;); - /* - * eliminate simple equalities using qe_lite - * then, MBP for Booleans (substitute), reals (based on LW), ints (based on Cooper), and arrays - */ - void qe_project_spacer(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { - th_rewriter rw(m); - TRACE("spacer_mbp", - tout << "Before projection:\n"; - tout << fml << "\n"; - tout << "Vars:\n" << vars;); + SASSERT(!m.is_false(fml)); - { - // Ensure that top-level AND of fml is flat - expr_ref_vector flat(m); - flatten_and(fml, flat); - fml = mk_and(flat); + // sort out vars into bools, arith (int/real), and arrays + for (app *v : vars) { + if (m.is_bool(v)) { + // obtain the interpretation of the ith var + // using model completion + model::scoped_model_completion _sc_(mdl, true); + bool_sub.insert(v, mdl(v)); + } else if (arr_u.is_array(v)) { + array_vars.push_back(v); + } else { + SASSERT(ari_u.is_int(v) || ari_u.is_real(v)); + arith_vars.push_back(v); + } } - - // uncomment for benchmarks - //to_mbp_benchmark(verbose_stream(), fml, vars); - - app_ref_vector arith_vars(m); - app_ref_vector array_vars(m); - array_util arr_u(m); - arith_util ari_u(m); - expr_safe_replace bool_sub(m); - expr_ref bval(m); - - while (true) { - params_ref p; - qe_lite qe(m, p, false); - qe(vars, fml); + // substitute Booleans + if (!bool_sub.empty()) { + bool_sub(fml); + // -- bool_sub is not simplifying rw(fml); - - TRACE("spacer_mbp", - tout << "After qe_lite:\n"; - tout << mk_pp(fml, m) << "\n"; - tout << "Vars:\n" << vars;); - SASSERT(!m.is_false(fml)); - - - // sort out vars into bools, arith (int/real), and arrays - for (app* v : vars) { - if (m.is_bool(v)) { - // obtain the interpretation of the ith var - // using model completion - model::scoped_model_completion _sc_(mdl, true); - bool_sub.insert(v, mdl(v)); - } - else if (arr_u.is_array(v)) { - array_vars.push_back(v); - } - else { - SASSERT(ari_u.is_int(v) || ari_u.is_real(v)); - arith_vars.push_back(v); - } - } - - // substitute Booleans - if (!bool_sub.empty()) { - bool_sub(fml); - // -- bool_sub is not simplifying - rw(fml); - SASSERT(!m.is_false(fml)); - TRACE("spacer_mbp", tout << "Projected Booleans:\n" << fml << "\n"; ); - bool_sub.reset(); - } - - TRACE("spacer_mbp", tout << "Array vars:\n"; tout << array_vars;); - - vars.reset(); - - // project arrays - { - scoped_no_proof _sp(m); - // -- local rewriter that is aware of current proof mode - th_rewriter srw(m); - spacer_qe::array_project(mdl, array_vars, fml, vars, reduce_all_selects); - SASSERT(array_vars.empty()); - srw(fml); - SASSERT(!m.is_false(fml)); - } - - TRACE("spacer_mbp", - tout << "extended model:\n"; - model_pp(tout, mdl); - tout << "Auxiliary variables of index and value sorts:\n"; - tout << vars;); - - if (vars.empty()) { break; } + TRACE("spacer_mbp", tout << "Projected Booleans:\n" + << fml << "\n";); + bool_sub.reset(); } - // project reals and ints - if (!arith_vars.empty()) { - TRACE("spacer_mbp", tout << "Arith vars:\n" << arith_vars;); - - if (use_native_mbp) { - qe::mbproj mbp(m); - expr_ref_vector fmls(m); - flatten_and(fml, fmls); - - mbp(true, arith_vars, mdl, fmls); - fml = mk_and(fmls); - SASSERT(arith_vars.empty()); - } - else { - scoped_no_proof _sp(m); - spacer_qe::arith_project(mdl, arith_vars, fml); - } - - TRACE("spacer_mbp", - tout << "Projected arith vars:\n" << fml << "\n"; - tout << "Remaining arith vars:\n" << arith_vars << "\n";); - SASSERT(!m.is_false(fml)); - } - - if (!arith_vars.empty()) { - mbqi_project(mdl, arith_vars, fml); - } - - // substitute any remaining arith vars - if (!dont_sub && !arith_vars.empty()) { - subst_vars(m, arith_vars, mdl, fml); - TRACE("spacer_mbp", - tout << "After substituting remaining arith vars:\n"; - tout << mk_pp(fml, m) << "\n"; - ); - // an extra round of simplification because subst_vars is not simplifying - rw(fml); - } - - DEBUG_CODE( - model_evaluator mev(mdl); - mev.set_model_completion(false); - SASSERT(mev.is_true(fml)); - ); + TRACE("spacer_mbp", tout << "Array vars:\n"; tout << array_vars;); vars.reset(); - if (dont_sub && !arith_vars.empty()) { - vars.append(arith_vars); + + // project arrays + { + scoped_no_proof _sp(m); + // -- local rewriter that is aware of current proof mode + th_rewriter srw(m); + spacer_qe::array_project(mdl, array_vars, fml, vars, + reduce_all_selects); + SASSERT(array_vars.empty()); + srw(fml); + SASSERT(!m.is_false(fml)); } + + TRACE("spacer_mbp", tout << "extended model:\n"; model_pp(tout, mdl); + tout << "Auxiliary variables of index and value sorts:\n"; + tout << vars;); + + if (vars.empty()) { break; } } + // project reals and ints + if (!arith_vars.empty()) { + TRACE("spacer_mbp", tout << "Arith vars:\n" << arith_vars;); - static expr* apply_accessor(ast_manager& m, - ptr_vector const& acc, - unsigned j, - func_decl* f, - expr* c) { - if (is_app(c) && to_app(c)->get_decl() == f) { - return to_app(c)->get_arg(j); - } - else { - return m.mk_app(acc[j], c); + if (use_native_mbp) { + qe::mbproj mbp(m); + expr_ref_vector fmls(m); + flatten_and(fml, fmls); + + mbp(true, arith_vars, mdl, fmls); + fml = mk_and(fmls); + SASSERT(arith_vars.empty()); + } else { + scoped_no_proof _sp(m); + spacer_qe::arith_project(mdl, arith_vars, fml); } + + TRACE("spacer_mbp", tout << "Projected arith vars:\n" + << fml << "\n"; + tout << "Remaining arith vars:\n" + << arith_vars << "\n";); + SASSERT(!m.is_false(fml)); } - void qe_project(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { - if (use_native_mbp) - qe_project_z3(m, vars, fml, mdl, - reduce_all_selects, use_native_mbp, dont_sub); - else - qe_project_spacer(m, vars, fml, mdl, - reduce_all_selects, use_native_mbp, dont_sub); + if (!arith_vars.empty()) { mbqi_project(mdl, arith_vars, fml); } + + // substitute any remaining arith vars + if (!dont_sub && !arith_vars.empty()) { + subst_vars(m, arith_vars, mdl, fml); + TRACE("spacer_mbp", + tout << "After substituting remaining arith vars:\n"; + tout << mk_pp(fml, m) << "\n";); + // an extra round of simplification because subst_vars is not + // simplifying + rw(fml); } - void expand_literals(ast_manager& m, expr_ref_vector& conjs) { - if (conjs.empty()) - return; - arith_util arith(m); - datatype_util dt(m); - bv_util bv(m); - expr* e1, * e2, * c, * val; - rational r; - unsigned bv_size; + DEBUG_CODE(model_evaluator mev(mdl); mev.set_model_completion(false); + SASSERT(mev.is_true(fml));); - TRACE("spacer_expand", tout << "begin expand\n" << conjs << "\n";); + vars.reset(); + if (dont_sub && !arith_vars.empty()) { vars.append(arith_vars); } +} - for (unsigned i = 0; i < conjs.size(); ++i) { - expr* e = conjs[i].get(); - if (m.is_eq(e, e1, e2) && arith.is_int_real(e1)) { - conjs[i] = arith.mk_le(e1, e2); - if (i + 1 == conjs.size()) { - conjs.push_back(arith.mk_ge(e1, e2)); +static expr *apply_accessor(ast_manager &m, ptr_vector const &acc, + unsigned j, func_decl *f, expr *c) { + if (is_app(c) && to_app(c)->get_decl() == f) { + return to_app(c)->get_arg(j); + } else { + return m.mk_app(acc[j], c); + } +} + +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects, bool use_native_mbp, bool dont_sub) { + if (use_native_mbp) + qe_project_z3(m, vars, fml, mdl, reduce_all_selects, use_native_mbp, + dont_sub); + else + qe_project_spacer(m, vars, fml, mdl, reduce_all_selects, use_native_mbp, + dont_sub); +} + +void expand_literals(ast_manager &m, expr_ref_vector &conjs) { + if (conjs.empty()) return; + arith_util arith(m); + datatype_util dt(m); + bv_util bv(m); + expr *e1, *e2, *c, *val; + rational r; + unsigned bv_size; + + TRACE("spacer_expand", tout << "begin expand\n" << conjs << "\n";); + + for (unsigned i = 0; i < conjs.size(); ++i) { + expr *e = conjs[i].get(); + if (m.is_eq(e, e1, e2) && arith.is_int_real(e1) && !arith.is_mod(e1) && + !arith.is_mod(e2)) { + conjs[i] = arith.mk_le(e1, e2); + if (i + 1 == conjs.size()) { + conjs.push_back(arith.mk_ge(e1, e2)); + } else { + conjs.push_back(conjs[i + 1].get()); + conjs[i + 1] = arith.mk_ge(e1, e2); + } + ++i; + } else if ((m.is_eq(e, c, val) && is_app(val) && + dt.is_constructor(to_app(val))) || + (m.is_eq(e, val, c) && is_app(val) && + dt.is_constructor(to_app(val)))) { + func_decl *f = to_app(val)->get_decl(); + func_decl *r = dt.get_constructor_is(f); + conjs[i] = m.mk_app(r, c); + ptr_vector const &acc = *dt.get_constructor_accessors(f); + for (unsigned j = 0; j < acc.size(); ++j) { + conjs.push_back(m.mk_eq(apply_accessor(m, acc, j, f, c), + to_app(val)->get_arg(j))); + } + } else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || + (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { + rational two(2); + for (unsigned j = 0; j < bv_size; ++j) { + parameter p(j); + expr *e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), + bv.mk_extract(j, j, c)); + if ((r % two).is_zero()) { e = m.mk_not(e); } + r = div(r, two); + if (j == 0) { + conjs[i] = e; + } else { + conjs.push_back(e); } - else { - conjs.push_back(conjs[i + 1].get()); - conjs[i + 1] = arith.mk_ge(e1, e2); + } + } + } + TRACE("spacer_expand", tout << "end expand\n" << conjs << "\n";); +} + +namespace { +class implicant_picker { + model &m_model; + ast_manager &m; + arith_util m_arith; + + expr_ref_vector m_todo; + expr_mark m_visited; + + // add literal to the implicant + // applies lightweight normalization + void add_literal(expr *e, expr_ref_vector &out) { + SASSERT(m.is_bool(e)); + + expr_ref res(m), v(m); + v = m_model(e); + // the literal must have a value + SASSERT(m.limit().is_canceled() || m.is_true(v) || m.is_false(v)); + + res = m.is_false(v) ? m.mk_not(e) : e; + + if (m.is_distinct(res)) { + // --(distinct a b) == (not (= a b)) + if (to_app(res)->get_num_args() == 2) { + res = m.mk_eq(to_app(res)->get_arg(0), to_app(res)->get_arg(1)); + res = m.mk_not(res); + } + } + + expr *nres = nullptr, *f1 = nullptr, *f2 = nullptr; + if (m.is_not(res, nres)) { + // --(not (xor a b)) == (= a b) + if (m.is_xor(nres, f1, f2)) res = m.mk_eq(f1, f2); + // -- split arithmetic inequality + else if (m.is_eq(nres, f1, f2) && m_arith.is_int_real(f1)) { + res = m_arith.mk_lt(f1, f2); + if (!m_model.is_true(res)) res = m_arith.mk_lt(f2, f1); + } + } + + if (!m_model.is_true(res)) { + IF_VERBOSE(2, verbose_stream() + << "(spacer-model-anomaly: " << res << ")\n"); + } + out.push_back(res); + } + + void process_app(app *a, expr_ref_vector &out) { + if (m_visited.is_marked(a)) return; + SASSERT(m.is_bool(a)); + expr_ref v(m); + v = m_model(a); + bool is_true = m.is_true(v); + + if (!is_true && !m.is_false(v)) return; + + expr *na = nullptr, *f1 = nullptr, *f2 = nullptr, *f3 = nullptr; + + SASSERT(!m.is_false(a)); + if (m.is_true(a)) { + // noop + } else if (a->get_family_id() != m.get_basic_family_id()) { + add_literal(a, out); + } else if (is_uninterp_const(a)) { + add_literal(a, out); + } else if (m.is_not(a, na)) { + m_todo.push_back(na); + } else if (m.is_distinct(a)) { + if (!is_true) { + expr_ref tmp = + mbp::project_plugin::pick_equality(m, m_model, a); + m_todo.push_back(tmp); + } else if (a->get_num_args() == 2) { + add_literal(a, out); + } else { + m_todo.push_back( + m.mk_distinct_expanded(a->get_num_args(), a->get_args())); + } + } else if (m.is_and(a)) { + if (is_true) { + m_todo.append(a->get_num_args(), a->get_args()); + } else { + for (expr *e : *a) { + if (m_model.is_false(e)) { + m_todo.push_back(e); + break; + } } + } + } else if (m.is_or(a)) { + if (!is_true) + m_todo.append(a->get_num_args(), a->get_args()); + else { + for (expr *e : *a) { + if (m_model.is_true(e)) { + m_todo.push_back(e); + break; + } + } + } + } else if (m.is_eq(a, f1, f2) || + (is_true && m.is_not(a, na) && m.is_xor(na, f1, f2))) { + if (!m.are_equal(f1, f2) && !m.are_distinct(f1, f2)) { + if (m.is_bool(f1) && + (!is_uninterp_const(f1) || !is_uninterp_const(f2))) + m_todo.append(a->get_num_args(), a->get_args()); + else + add_literal(a, out); + } + } else if (m.is_ite(a, f1, f2, f3)) { + if (m.are_equal(f2, f3)) { + m_todo.push_back(f2); + } else if (m_model.is_true(f2) && m_model.is_true(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } else if (m_model.is_false(f2) && m_model.is_false(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } else if (m_model.is_true(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f2); + } else if (m_model.is_false(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f3); + } + } else if (m.is_xor(a, f1, f2)) { + m_todo.append(a->get_num_args(), a->get_args()); + } else if (m.is_implies(a, f1, f2)) { + if (is_true) { + if (m_model.is_true(f2)) + m_todo.push_back(f2); + else if (m_model.is_false(f1)) + m_todo.push_back(f1); + } else + m_todo.append(a->get_num_args(), a->get_args()); + } else { + IF_VERBOSE(0, verbose_stream() << "Unexpected expression: " + << mk_pp(a, m) << "\n"); + UNREACHABLE(); + } + } + + void pick_literals(expr *e, expr_ref_vector &out) { + SASSERT(m_todo.empty()); + if (m_visited.is_marked(e) || !is_app(e)) return; + + // -- keep track of all created expressions to + // -- make sure that expression ids are stable + expr_ref_vector pinned(m); + + m_todo.reset(); + m_todo.push_back(e); + while (!m_todo.empty()) { + pinned.push_back(m_todo.back()); + m_todo.pop_back(); + if (!is_app(pinned.back())) continue; + app *a = to_app(pinned.back()); + process_app(a, out); + m_visited.mark(a, true); + } + m_todo.reset(); + } + + bool pick_implicant(const expr_ref_vector &in, expr_ref_vector &out) { + m_visited.reset(); + bool is_true = m_model.is_true(in); + + for (expr *e : in) { + if (is_true || m_model.is_true(e)) { pick_literals(e, out); } + } + m_visited.reset(); + return is_true; + } + + public: + implicant_picker(model &mdl) + : m_model(mdl), m(m_model.get_manager()), m_arith(m), m_todo(m) {} + + void operator()(expr_ref_vector &in, expr_ref_vector &out) { + model::scoped_model_completion _sc_(m_model, false); + pick_implicant(in, out); + } +}; +} // namespace + +expr_ref_vector compute_implicant_literals(model &mdl, + expr_ref_vector &formula) { + // flatten the formula and remove all trivial literals + + // TBD: not clear why there is a dependence on it(other than + // not handling of Boolean constants by implicant_picker), however, + // it was a source of a problem on a benchmark + expr_ref_vector res(formula.get_manager()); + flatten_and(formula); + if (!formula.empty()) { + implicant_picker ipick(mdl); + ipick(formula, res); + } + return res; +} + +void simplify_bounds_old(expr_ref_vector &cube) { + ast_manager &m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr *c : cube) g->assert_expr(c); + + goal_ref_buffer result; + tactic_ref simplifier = mk_arith_bounds_tactic(m); + (*simplifier)(g, result); + SASSERT(result.size() == 1); + goal *r = result[0]; + cube.reset(); + for (unsigned i = 0; i < r->size(); ++i) { cube.push_back(r->form(i)); } +} + +void simplify_bounds_new(expr_ref_vector &cube) { + ast_manager &m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr *c : cube) g->assert_expr(c); + + goal_ref_buffer goals; + tactic_ref prop_values = mk_propagate_values_tactic(m); + tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); + tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); + + (*t)(g, goals); + SASSERT(goals.size() == 1); + + g = goals[0]; + cube.reset(); + for (unsigned i = 0; i < g->size(); ++i) { cube.push_back(g->form(i)); } +} + +void simplify_bounds(expr_ref_vector &cube) { simplify_bounds_new(cube); } + +/// Adhoc rewriting of arithmetic expressions +struct adhoc_rewriter_cfg : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + + adhoc_rewriter_cfg(ast_manager &manager) : m(manager), m_arith(m) {} + + bool is_le(func_decl const *n) const { return m_arith.is_le(n); } + bool is_ge(func_decl const *n) const { return m_arith.is_ge(n); } + + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + expr *e; + if (is_le(f)) return mk_le_core(args[0], args[1], result); + if (is_ge(f)) return mk_ge_core(args[0], args[1], result); + if (m.is_not(f) && m.is_not(args[0], e)) { + result = e; + return BR_DONE; + } + return BR_FAILED; + } + + br_status mk_le_core(expr *arg1, expr *arg2, expr_ref &result) { + // t <= -1 ==> t < 0 ==> !(t >= 0) + if (m_arith.is_int(arg1) && m_arith.is_minus_one(arg2)) { + result = m.mk_not(m_arith.mk_ge(arg1, mk_zero())); + return BR_DONE; + } + return BR_FAILED; + } + br_status mk_ge_core(expr *arg1, expr *arg2, expr_ref &result) { + // t >= 1 ==> t > 0 ==> !(t <= 0) + if (m_arith.is_int(arg1) && is_one(arg2)) { + + result = m.mk_not(m_arith.mk_le(arg1, mk_zero())); + return BR_DONE; + } + return BR_FAILED; + } + expr *mk_zero() { return m_arith.mk_numeral(rational(0), true); } + bool is_one(expr const *n) const { + rational val; + return m_arith.is_numeral(n, val) && val.is_one(); + } +}; + +bool is_normalized(expr_ref e, bool use_simplify_bounds, bool use_factor_eqs) { + expr_ref out(e.m()); + normalize(e, out, use_simplify_bounds, use_factor_eqs); + + expr_ref out0 = out; + if (e != out) { normalize(out, out, use_simplify_bounds, use_factor_eqs); } + + CTRACE("inherit_bug", e != out, + tout << "e==out0: " << (e == out0) << " e==out: " << (e == out) + << " out0==out: " << (out0 == out) << "\n"; + tout << "e: " << e << "\n" + << "out0: " << out0 << "\n" + << "out: " << out << "\n";); + return e == out; +} +void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, + bool use_factor_eqs) { + + ast_manager &m = out.m(); + params_ref params; + // arith_rewriter + params.set_bool("sort_sums", true); + params.set_bool("gcd_rounding", true); + // params.set_bool("arith_lhs", true); + params.set_bool("arith_ineq_lhs", true); + // poly_rewriter + params.set_bool("som", true); + params.set_bool("flat", true); + + // apply rewriter + th_rewriter rw(m, params); + rw(e, out); + + // adhoc_rewriter_cfg adhoc_cfg(m); + // rewriter_tpl adhoc_rw(m, false, adhoc_cfg); + // adhoc_rw(out.get(), out); + + if (m.is_and(out)) { + expr_ref_vector v(m); + flatten_and(out, v); + + if (v.size() > 1) { + if (use_simplify_bounds) { + // remove redundant inequalities + simplify_bounds(v); + } + if (use_factor_eqs) { + // -- refactor equivalence classes and choose a representative + mbp::term_graph egraph(out.m()); + egraph.add_lits(v); + v.reset(); + egraph.to_lits(v); + } + // sort arguments of the top-level and + std::stable_sort(v.data(), v.data() + v.size(), ast_lt_proc()); + + TRACE("spacer_normalize", tout << "Normalized:\n" + << out << "\n" + << "to\n" + << mk_and(v) << "\n";); + TRACE("spacer_normalize", { + mbp::term_graph egraph(m); + for (expr *e : v) egraph.add_lit(to_app(e)); + tout << "Reduced app:\n" << mk_pp(egraph.to_expr(), m) << "\n"; + }); + out = mk_and(v); + } + } + + // normalize_order(out, out); +} + +// rewrite term such that the pretty printing is easier to read +struct adhoc_rewriter_rpp : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + + adhoc_rewriter_rpp(ast_manager &manager) : m(manager), m_arith(m) {} + + bool is_le(func_decl const *n) const { return m_arith.is_le(n); } + bool is_ge(func_decl const *n) const { return m_arith.is_ge(n); } + bool is_lt(func_decl const *n) const { return m_arith.is_lt(n); } + bool is_gt(func_decl const *n) const { return m_arith.is_gt(n); } + bool is_zero(expr const *n) const { + rational val; + return m_arith.is_numeral(n, val) && val.is_zero(); + } + + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + br_status st = BR_FAILED; + expr *e1, *e2, *e3, *e4; + + // rewrites(=(+ A(* -1 B)) 0) into(= A B) + if (m.is_eq(f) && is_zero(args[1]) && m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { + result = m.mk_eq(e1, e4); + return BR_DONE; + } + // simplify normalized leq, where right side is different from 0 + // rewrites(<=(+ A(* -1 B)) C) into(<= A B+C) + else if ((is_le(f) || is_lt(f) || is_ge(f) || is_gt(f)) && + m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { + expr_ref rhs(m); + rhs = is_zero(args[1]) ? e4 : m_arith.mk_add(e4, args[1]); + + if (is_le(f)) { + result = m_arith.mk_le(e1, rhs); + st = BR_DONE; + } else if (is_lt(f)) { + result = m_arith.mk_lt(e1, rhs); + st = BR_DONE; + } else if (is_ge(f)) { + result = m_arith.mk_ge(e1, rhs); + st = BR_DONE; + } else if (is_gt(f)) { + result = m_arith.mk_gt(e1, rhs); + st = BR_DONE; + } else { + UNREACHABLE(); + } + } + // simplify negation of ordering predicate + else if (m.is_not(f)) { + if (m_arith.is_lt(args[0], e1, e2)) { + result = m_arith.mk_ge(e1, e2); + st = BR_DONE; + } else if (m_arith.is_le(args[0], e1, e2)) { + result = m_arith.mk_gt(e1, e2); + st = BR_DONE; + } else if (m_arith.is_gt(args[0], e1, e2)) { + result = m_arith.mk_le(e1, e2); + st = BR_DONE; + } else if (m_arith.is_ge(args[0], e1, e2)) { + result = m_arith.mk_lt(e1, e2); + st = BR_DONE; + } + } + return st; + } +}; + +mk_epp::mk_epp(ast *t, ast_manager &m, unsigned indent, unsigned num_vars, + char const *var_prefix) + : mk_pp(t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { + m_epp_params.set_uint("min_alias_size", UINT_MAX); + m_epp_params.set_uint("max_depth", UINT_MAX); + + if (is_expr(m_ast)) { + rw(to_expr(m_ast), m_epp_expr); + m_ast = m_epp_expr; + } +} + +void mk_epp::rw(expr *e, expr_ref &out) { + adhoc_rewriter_rpp cfg(out.m()); + rewriter_tpl arw(out.m(), false, cfg); + arw(e, out); +} + +void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars) { + expr_free_vars fv; + ast_manager &m = out.m(); + + fv(e); + if (vars.size() < fv.size()) { vars.resize(fv.size()); } + for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { + sort *s = fv[i] ? fv[i] : m.mk_bool_sort(); + vars[i] = mk_zk_const(m, i, s); + var_subst vs(m, false); + out = vs(e, vars.size(), (expr **)vars.data()); + } +} + +struct index_term_finder { + ast_manager &m; + array_util m_array; + app_ref m_var; + expr_ref_vector &m_res; + + index_term_finder(ast_manager &mgr, app *v, expr_ref_vector &res) + : m(mgr), m_array(m), m_var(v, m), m_res(res) {} + void operator()(var *n) {} + void operator()(quantifier *n) {} + void operator()(app *n) { + if (m_array.is_select(n) || m.is_eq(n)) { + unsigned i = 0; + for (expr *arg : *n) { + if ((m.is_eq(n) || i > 0) && m_var != arg) m_res.push_back(arg); ++i; } - else if ((m.is_eq(e, c, val) && is_app(val) && dt.is_constructor(to_app(val))) || - (m.is_eq(e, val, c) && is_app(val) && dt.is_constructor(to_app(val)))) { - func_decl* f = to_app(val)->get_decl(); - func_decl* r = dt.get_constructor_is(f); - conjs[i] = m.mk_app(r, c); - ptr_vector const& acc = *dt.get_constructor_accessors(f); - for (unsigned j = 0; j < acc.size(); ++j) { - conjs.push_back(m.mk_eq(apply_accessor(m, acc, j, f, c), to_app(val)->get_arg(j))); - } - } - else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || - (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { - rational two(2); - for (unsigned j = 0; j < bv_size; ++j) { - parameter p(j); - expr* e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), bv.mk_extract(j, j, c)); - if ((r % two).is_zero()) { - e = m.mk_not(e); - } - r = div(r, two); - if (j == 0) { - conjs[i] = e; - } - else { - conjs.push_back(e); - } - } - } - } - TRACE("spacer_expand", tout << "end expand\n" << conjs << "\n";); - } - - namespace { - class implicant_picker { - model& m_model; - ast_manager& m; - arith_util m_arith; - - expr_ref_vector m_todo; - expr_mark m_visited; - - // add literal to the implicant - // applies lightweight normalization - void add_literal(expr* e, expr_ref_vector& out) { - SASSERT(m.is_bool(e)); - - expr_ref res(m), v(m); - v = m_model(e); - // the literal must have a value - SASSERT(m.limit().is_canceled() || m.is_true(v) || m.is_false(v)); - - res = m.is_false(v) ? m.mk_not(e) : e; - - if (m.is_distinct(res)) { - // --(distinct a b) == (not (= a b)) - if (to_app(res)->get_num_args() == 2) { - res = m.mk_eq(to_app(res)->get_arg(0), - to_app(res)->get_arg(1)); - res = m.mk_not(res); - } - } - - expr* nres = nullptr, * f1 = nullptr, * f2 = nullptr; - if (m.is_not(res, nres)) { - // --(not (xor a b)) == (= a b) - if (m.is_xor(nres, f1, f2)) - res = m.mk_eq(f1, f2); - // -- split arithmetic inequality - else if (m.is_eq(nres, f1, f2) && m_arith.is_int_real(f1)) { - res = m_arith.mk_lt(f1, f2); - if (!m_model.is_true(res)) - res = m_arith.mk_lt(f2, f1); - } - } - - - if (!m_model.is_true(res)) { - IF_VERBOSE(2, verbose_stream() - << "(spacer-model-anomaly: " << res << ")\n"); - } - out.push_back(res); - } - - void process_app(app* a, expr_ref_vector& out) { - if (m_visited.is_marked(a)) return; - SASSERT(m.is_bool(a)); - expr_ref v(m); - v = m_model(a); - bool is_true = m.is_true(v); - - if (!is_true && !m.is_false(v)) return; - - expr* na = nullptr, * f1 = nullptr, * f2 = nullptr, * f3 = nullptr; - - SASSERT(!m.is_false(a)); - if (m.is_true(a)) { - // noop - } - else if (a->get_family_id() != m.get_basic_family_id()) { - add_literal(a, out); - } - else if (is_uninterp_const(a)) { - add_literal(a, out); - } - else if (m.is_not(a, na)) { - m_todo.push_back(na); - } - else if (m.is_distinct(a)) { - if (!is_true) { - expr_ref tmp = mbp::project_plugin::pick_equality(m, m_model, a); - m_todo.push_back(tmp); - } - else if (a->get_num_args() == 2) { - add_literal(a, out); - } - else { - m_todo.push_back(m.mk_distinct_expanded(a->get_num_args(), - a->get_args())); - } - } - else if (m.is_and(a)) { - if (is_true) { - m_todo.append(a->get_num_args(), a->get_args()); - } - else { - for (expr* e : *a) { - if (m_model.is_false(e)) { - m_todo.push_back(e); - break; - } - } - } - } - else if (m.is_or(a)) { - if (!is_true) - m_todo.append(a->get_num_args(), a->get_args()); - else { - for (expr* e : *a) { - if (m_model.is_true(e)) { - m_todo.push_back(e); - break; - } - } - } - } - else if (m.is_eq(a, f1, f2) || - (is_true && m.is_not(a, na) && m.is_xor(na, f1, f2))) { - if (!m.are_equal(f1, f2) && !m.are_distinct(f1, f2)) { - if (m.is_bool(f1) && - (!is_uninterp_const(f1) || !is_uninterp_const(f2))) - m_todo.append(a->get_num_args(), a->get_args()); - else - add_literal(a, out); - } - } - else if (m.is_ite(a, f1, f2, f3)) { - if (m.are_equal(f2, f3)) { - m_todo.push_back(f2); - } - else if (m_model.is_true(f2) && m_model.is_true(f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } - else if (m_model.is_false(f2) && m_model.is_false(f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } - else if (m_model.is_true(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f2); - } - else if (m_model.is_false(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f3); - } - } - else if (m.is_xor(a, f1, f2)) { - m_todo.append(a->get_num_args(), a->get_args()); - } - else if (m.is_implies(a, f1, f2)) { - if (is_true) { - if (m_model.is_true(f2)) - m_todo.push_back(f2); - else if (m_model.is_false(f1)) - m_todo.push_back(f1); - } - else - m_todo.append(a->get_num_args(), a->get_args()); - } - else { - IF_VERBOSE(0, - verbose_stream() << "Unexpected expression: " - << mk_pp(a, m) << "\n"); - UNREACHABLE(); - } - } - - void pick_literals(expr* e, expr_ref_vector& out) { - SASSERT(m_todo.empty()); - if (m_visited.is_marked(e) || !is_app(e)) return; - - // -- keep track of all created expressions to - // -- make sure that expression ids are stable - expr_ref_vector pinned(m); - - m_todo.reset(); - m_todo.push_back(e); - while (!m_todo.empty()) { - pinned.push_back(m_todo.back()); - m_todo.pop_back(); - if (!is_app(pinned.back())) continue; - app* a = to_app(pinned.back()); - process_app(a, out); - m_visited.mark(a, true); - } - m_todo.reset(); - } - - bool pick_implicant(const expr_ref_vector& in, expr_ref_vector& out) { - m_visited.reset(); - bool is_true = m_model.is_true(in); - - for (expr* e : in) { - if (is_true || m_model.is_true(e)) { - pick_literals(e, out); - } - } - m_visited.reset(); - return is_true; - } - - public: - - implicant_picker(model& mdl) : - m_model(mdl), m(m_model.get_manager()), m_arith(m), m_todo(m) {} - - void operator()(expr_ref_vector& in, expr_ref_vector& out) { - model::scoped_model_completion _sc_(m_model, false); - pick_implicant(in, out); - } - }; - } - - expr_ref_vector compute_implicant_literals(model& mdl, - expr_ref_vector& formula) { - // flatten the formula and remove all trivial literals - - // TBD: not clear why there is a dependence on it(other than - // not handling of Boolean constants by implicant_picker), however, - // it was a source of a problem on a benchmark - expr_ref_vector res(formula.get_manager()); - flatten_and(formula); - if (!formula.empty()) { - implicant_picker ipick(mdl); - ipick(formula, res); - } - return res; - } - - void simplify_bounds_old(expr_ref_vector& cube) { - ast_manager& m = cube.m(); - scoped_no_proof _no_pf_(m); - goal_ref g(alloc(goal, m, false, false, false)); - for (expr* c : cube) - g->assert_expr(c); - - goal_ref_buffer result; - tactic_ref simplifier = mk_arith_bounds_tactic(m); - (*simplifier)(g, result); - SASSERT(result.size() == 1); - goal* r = result[0]; - cube.reset(); - for (unsigned i = 0; i < r->size(); ++i) { - cube.push_back(r->form(i)); } } +}; - void simplify_bounds_new(expr_ref_vector& cube) { - ast_manager& m = cube.m(); - scoped_no_proof _no_pf_(m); - goal_ref g(alloc(goal, m, false, false, false)); - for (expr* c : cube) - g->assert_expr(c); +bool mbqi_project_var(model &mdl, app *var, expr_ref &fml) { + ast_manager &m = fml.get_manager(); + model::scoped_model_completion _sc_(mdl, false); - goal_ref_buffer goals; - tactic_ref prop_values = mk_propagate_values_tactic(m); - tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); - tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); + expr_ref val(m); + val = mdl(var); - (*t)(g, goals); - SASSERT(goals.size() == 1); + TRACE("mbqi_project_verbose", tout << "MBQI: var: " << mk_pp(var, m) << "\n" + << "fml: " << fml << "\n";); + expr_ref_vector terms(m); + index_term_finder finder(m, var, terms); + for_each_expr(finder, fml); - g = goals[0]; - cube.reset(); - for (unsigned i = 0; i < g->size(); ++i) { - cube.push_back(g->form(i)); - } - } + TRACE("mbqi_project_verbose", tout << "terms:\n" << terms << "\n";); - void simplify_bounds(expr_ref_vector& cube) { - simplify_bounds_new(cube); - } + for (expr *term : terms) { + expr_ref tval(m); + tval = mdl(term); - /// Adhoc rewriting of arithmetic expressions - struct adhoc_rewriter_cfg : public default_rewriter_cfg { - ast_manager& m; - arith_util m_util; + TRACE("mbqi_project_verbose", tout << "term: " << mk_pp(term, m) + << " tval: " << tval + << " val: " << val << "\n";); - adhoc_rewriter_cfg(ast_manager& manager) : m(manager), m_util(m) {} - - bool is_le(func_decl const* n) const { return m_util.is_le(n); } - bool is_ge(func_decl const* n) const { return m_util.is_ge(n); } - - br_status reduce_app(func_decl* f, unsigned num, expr* const* args, - expr_ref& result, proof_ref& result_pr) { - expr* e; - if (is_le(f)) - return mk_le_core(args[0], args[1], result); - if (is_ge(f)) - return mk_ge_core(args[0], args[1], result); - if (m.is_not(f) && m.is_not(args[0], e)) { - result = e; - return BR_DONE; - } - return BR_FAILED; - } - - br_status mk_le_core(expr* arg1, expr* arg2, expr_ref& result) { - // t <= -1 ==> t < 0 ==> !(t >= 0) - if (m_util.is_int(arg1) && m_util.is_minus_one(arg2)) { - result = m.mk_not(m_util.mk_ge(arg1, mk_zero())); - return BR_DONE; - } - return BR_FAILED; - } - br_status mk_ge_core(expr* arg1, expr* arg2, expr_ref& result) { - // t >= 1 ==> t > 0 ==> !(t <= 0) - if (m_util.is_int(arg1) && is_one(arg2)) { - - result = m.mk_not(m_util.mk_le(arg1, mk_zero())); - return BR_DONE; - } - return BR_FAILED; - } - expr* mk_zero() { return m_util.mk_numeral(rational(0), true); } - bool is_one(expr const* n) const { - rational val; return m_util.is_numeral(n, val) && val.is_one(); - } - }; - - void normalize(expr* e, expr_ref& out, - bool use_simplify_bounds, - bool use_factor_eqs) - { - - params_ref params; - // arith_rewriter - params.set_bool("sort_sums", true); - params.set_bool("gcd_rounding", true); - params.set_bool("arith_lhs", true); - // poly_rewriter - params.set_bool("som", true); - params.set_bool("flat", true); - - // apply rewriter - th_rewriter rw(out.m(), params); - rw(e, out); - - adhoc_rewriter_cfg adhoc_cfg(out.m()); - rewriter_tpl adhoc_rw(out.m(), false, adhoc_cfg); - adhoc_rw(out.get(), out); - - if (out.m().is_and(out)) { - expr_ref_vector v(out.m()); - flatten_and(out, v); - - if (v.size() > 1) { - if (use_simplify_bounds) { - // remove redundant inequalities - simplify_bounds(v); - } - if (use_factor_eqs) { - // -- refactor equivalence classes and choose a representative - mbp::term_graph egraph(out.m()); - egraph.add_lits(v); - v.reset(); - egraph.to_lits(v); - } - // sort arguments of the top-level and - std::stable_sort(v.data(), v.data() + v.size(), ast_lt_proc()); - - TRACE("spacer_normalize", - tout << "Normalized:\n" - << out << "\n" - << "to\n" - << mk_and(v) << "\n";); - TRACE("spacer_normalize", - mbp::term_graph egraph(out.m()); - for (expr* e : v) egraph.add_lit(to_app(e)); - tout << "Reduced app:\n" - << mk_pp(egraph.to_expr(), out.m()) << "\n";); - out = mk_and(v); - } - } - } - - // rewrite term such that the pretty printing is easier to read - struct adhoc_rewriter_rpp : public default_rewriter_cfg { - ast_manager& m; - arith_util m_arith; - - adhoc_rewriter_rpp(ast_manager& manager) : m(manager), m_arith(m) {} - - bool is_le(func_decl const* n) const { return m_arith.is_le(n); } - bool is_ge(func_decl const* n) const { return m_arith.is_ge(n); } - bool is_lt(func_decl const* n) const { return m_arith.is_lt(n); } - bool is_gt(func_decl const* n) const { return m_arith.is_gt(n); } - bool is_zero(expr const* n) const { rational val; return m_arith.is_numeral(n, val) && val.is_zero(); } - - br_status reduce_app(func_decl* f, unsigned num, expr* const* args, - expr_ref& result, proof_ref& result_pr) - { - br_status st = BR_FAILED; - expr* e1, * e2, * e3, * e4; - - // rewrites(=(+ A(* -1 B)) 0) into(= A B) - if (m.is_eq(f) && is_zero(args[1]) && - m_arith.is_add(args[0], e1, e2) && - m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { - result = m.mk_eq(e1, e4); - return BR_DONE; - } - // simplify normalized leq, where right side is different from 0 - // rewrites(<=(+ A(* -1 B)) C) into(<= A B+C) - else if ((is_le(f) || is_lt(f) || is_ge(f) || is_gt(f)) && - m_arith.is_add(args[0], e1, e2) && - m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { - expr_ref rhs(m); - rhs = is_zero(args[1]) ? e4 : m_arith.mk_add(e4, args[1]); - - if (is_le(f)) { - result = m_arith.mk_le(e1, rhs); - st = BR_DONE; - } - else if (is_lt(f)) { - result = m_arith.mk_lt(e1, rhs); - st = BR_DONE; - } - else if (is_ge(f)) { - result = m_arith.mk_ge(e1, rhs); - st = BR_DONE; - } - else if (is_gt(f)) { - result = m_arith.mk_gt(e1, rhs); - st = BR_DONE; - } - else - { - UNREACHABLE(); - } - } - // simplify negation of ordering predicate - else if (m.is_not(f)) { - if (m_arith.is_lt(args[0], e1, e2)) { - result = m_arith.mk_ge(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_le(args[0], e1, e2)) { - result = m_arith.mk_gt(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_gt(args[0], e1, e2)) { - result = m_arith.mk_le(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_ge(args[0], e1, e2)) { - result = m_arith.mk_lt(e1, e2); - st = BR_DONE; - } - } - return st; - } - }; - - mk_epp::mk_epp(ast* t, ast_manager& m, unsigned indent, - unsigned num_vars, char const* var_prefix) : - mk_pp(t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { - m_epp_params.set_uint("min_alias_size", UINT_MAX); - m_epp_params.set_uint("max_depth", UINT_MAX); - - if (is_expr(m_ast)) { - rw(to_expr(m_ast), m_epp_expr); - m_ast = m_epp_expr; - } - } - - void mk_epp::rw(expr* e, expr_ref& out) { - adhoc_rewriter_rpp cfg(out.m()); - rewriter_tpl arw(out.m(), false, cfg); - arw(e, out); - } - - void ground_expr(expr* e, expr_ref& out, app_ref_vector& vars) { - expr_free_vars fv; - ast_manager& m = out.m(); - - fv(e); - if (vars.size() < fv.size()) { - vars.resize(fv.size()); - } - for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { - sort* s = fv[i] ? fv[i] : m.mk_bool_sort(); - vars[i] = mk_zk_const(m, i, s); - var_subst vs(m, false); - out = vs(e, vars.size(), (expr**)vars.data()); - } - } - - struct index_term_finder { - ast_manager& m; - array_util m_array; - app_ref m_var; - expr_ref_vector& m_res; - - index_term_finder(ast_manager& mgr, app* v, expr_ref_vector& res) : m(mgr), m_array(m), m_var(v, m), m_res(res) {} - void operator()(var* n) {} - void operator()(quantifier* n) {} - void operator()(app* n) { - if (m_array.is_select(n) || m.is_eq(n)) { - unsigned i = 0; - for (expr* arg : *n) { - if ((m.is_eq(n) || i > 0) && m_var != arg) m_res.push_back(arg); - ++i; - } - } - } - }; - - bool mbqi_project_var(model& mdl, app* var, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - model::scoped_model_completion _sc_(mdl, false); - - expr_ref val(m); - val = mdl(var); - - TRACE("mbqi_project_verbose", - tout << "MBQI: var: " << mk_pp(var, m) << "\n" - << "fml: " << fml << "\n";); - expr_ref_vector terms(m); - index_term_finder finder(m, var, terms); - for_each_expr(finder, fml); - - TRACE("mbqi_project_verbose", tout << "terms:\n" << terms << "\n";); - - for (expr* term : terms) { - expr_ref tval(m); - tval = mdl(term); - - TRACE("mbqi_project_verbose", - tout << "term: " << mk_pp(term, m) - << " tval: " << tval << " val: " << val << "\n";); - - // -- if the term does not contain an occurrence of var - // -- and is in the same equivalence class in the model - if (tval == val && !occurs(var, term)) { - TRACE("mbqi_project", - tout << "MBQI: replacing " << mk_pp(var, m) - << " with " << mk_pp(term, m) << "\n";); - expr_safe_replace sub(m); - sub.insert(var, term); - sub(fml); - return true; - } - } - - TRACE("mbqi_project", - tout << "MBQI: failed to eliminate " << mk_pp(var, m) - << " from " << fml << "\n";); - - return false; - } - - void mbqi_project(model& mdl, app_ref_vector& vars, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - expr_ref tmp(m); - model::scoped_model_completion _sc_(mdl, false); - // -- evaluate to initialize mev cache - tmp = mdl(fml); - tmp.reset(); - - unsigned j = 0; - for (app* v : vars) - if (!mbqi_project_var(mdl, v, fml)) - vars[j++] = v; - vars.shrink(j); - } - - struct found {}; - struct check_select { - array_util a; - check_select(ast_manager& m) : a(m) {} - void operator()(expr* n) {} - void operator()(app* n) { if (a.is_select(n)) throw found(); } - }; - - bool contains_selects(expr* fml, ast_manager& m) { - check_select cs(m); - try { - for_each_expr(cs, fml); - return false; - } - catch (const found&) { + // -- if the term does not contain an occurrence of var + // -- and is in the same equivalence class in the model + if (tval == val && !occurs(var, term)) { + TRACE("mbqi_project", tout << "MBQI: replacing " << mk_pp(var, m) + << " with " << mk_pp(term, m) << "\n";); + expr_safe_replace sub(m); + sub.insert(var, term); + sub(fml); return true; } } - struct collect_indices { - app_ref_vector& m_indices; - array_util a; - collect_indices(app_ref_vector& indices) : m_indices(indices), - a(indices.get_manager()) {} - void operator()(expr* n) {} - void operator()(app* n) { - if (a.is_select(n)) { - // for all but first argument - for (unsigned i = 1; i < n->get_num_args(); ++i) { - expr* arg = n->get_arg(i); - if (is_app(arg)) - m_indices.push_back(to_app(arg)); - } + TRACE("mbqi_project", tout << "MBQI: failed to eliminate " << mk_pp(var, m) + << " from " << fml << "\n";); + + return false; +} + +void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml) { + ast_manager &m = fml.get_manager(); + expr_ref tmp(m); + model::scoped_model_completion _sc_(mdl, false); + // -- evaluate to initialize mev cache + tmp = mdl(fml); + tmp.reset(); + + unsigned j = 0; + for (app *v : vars) + if (!mbqi_project_var(mdl, v, fml)) vars[j++] = v; + vars.shrink(j); +} + +struct found {}; +struct check_select { + array_util a; + check_select(ast_manager &m) : a(m) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (a.is_select(n)) throw found(); + } +}; + +bool contains_selects(expr *fml, ast_manager &m) { + check_select cs(m); + try { + for_each_expr(cs, fml); + return false; + } catch (const found &) { return true; } +} + +struct collect_indices { + app_ref_vector &m_indices; + array_util a; + collect_indices(app_ref_vector &indices) + : m_indices(indices), a(indices.get_manager()) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (a.is_select(n)) { + // for all but first argument + for (unsigned i = 1; i < n->get_num_args(); ++i) { + expr *arg = n->get_arg(i); + if (is_app(arg)) m_indices.push_back(to_app(arg)); } } - }; - - void get_select_indices(expr* fml, app_ref_vector& indices) { - collect_indices ci(indices); - for_each_expr(ci, fml); } +}; - struct collect_decls { - app_ref_vector& m_decls; - std::string& prefix; - collect_decls(app_ref_vector& decls, std::string& p) : m_decls(decls), prefix(p) {} - void operator()(expr* n) {} - void operator()(app* n) { - if (n->get_decl()->get_name().str().find(prefix) != std::string::npos) - m_decls.push_back(n); - } - }; +void get_select_indices(expr *fml, app_ref_vector &indices) { + collect_indices ci(indices); + for_each_expr(ci, fml); +} - void find_decls(expr* fml, app_ref_vector& decls, std::string& prefix) { - collect_decls cd(decls, prefix); - for_each_expr(cd, fml); +struct collect_decls { + app_ref_vector &m_decls; + std::string &prefix; + collect_decls(app_ref_vector &decls, std::string &p) + : m_decls(decls), prefix(p) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (n->get_decl()->get_name().str().find(prefix) != std::string::npos) + m_decls.push_back(n); } +}; - // set the value of a boolean function to true in model - void set_true_in_mdl(model& model, func_decl* f) { - SASSERT(f->get_arity() == 0); - model.unregister_decl(f); - model.register_decl(f, model.get_manager().mk_true()); - model.reset_eval_cache(); +void find_decls(expr *fml, app_ref_vector &decls, std::string &prefix) { + collect_decls cd(decls, prefix); + for_each_expr(cd, fml); +} + +// set the value of a boolean function to true in model +void set_true_in_mdl(model &model, func_decl *f) { + SASSERT(f->get_arity() == 0); + model.unregister_decl(f); + model.register_decl(f, model.get_manager().mk_true()); + model.reset_eval_cache(); +} + +// Return number of variables in \p e +unsigned get_num_vars(expr *e) { + expr_free_vars fv; + fv(e); + unsigned count = 0; + for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { + if (fv[i]) { count++; } } + return count; +} + +namespace collect_uninterp_consts_ns { +struct proc { + expr_ref_vector &m_out; + proc(expr_ref_vector &out) : m_out(out) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (is_uninterp_const(n)) m_out.push_back(n); + } +}; +} // namespace collect_uninterp_consts_ns + +// Return all uninterpreted constants of \p q +void collect_uninterp_consts(expr *e, expr_ref_vector &out) { + collect_uninterp_consts_ns::proc proc(out); + for_each_expr(proc, e); +} + +namespace has_nonlinear_var_mul_ns { +struct found {}; +// Detects multiplication of a variable by not-a-number +struct proc { + arith_util m_arith; + bv_util m_bv; + proc(ast_manager &m) : m_arith(m), m_bv(m) {} + bool is_numeral(expr *e) const { + return m_arith.is_numeral(e) || m_bv.is_numeral(e); + } + bool is_mul(const expr *n, expr *&e1, expr *&e2) const { + if (m_arith.is_mul(n, e1, e2)) return true; + if (m_bv.is_bv_mul(n, e1, e2)) return true; + return false; + } + void operator()(var *n) const {} + void operator()(quantifier *q) const {} + void operator()(app const *n) const { + expr *e1, *e2; + if (is_mul(n, e1, e2) && ((is_var(e1) && !is_numeral(e2)) || + (is_var(e2) && !is_numeral(e1)))) + throw found(); + } +}; +} // namespace has_nonlinear_var_mul_ns + +// Returns true if \p e contains a multiplication a variable by not-a-number +bool has_nonlinear_var_mul(expr *e, ast_manager &m) { + has_nonlinear_var_mul_ns::proc proc(m); + try { + for_each_expr(proc, e); + } catch (const has_nonlinear_var_mul_ns::found &) { return true; } + return false; +} + +namespace contains_mod_ns { +struct found {}; +struct contains_mod_proc { + ast_manager &m; + arith_util m_arith; + contains_mod_proc(ast_manager &a_m) : m(a_m), m_arith(m) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (m_arith.is_mod(n)) throw found(); + } +}; +} // namespace contains_mod_ns + +// Returns true if \p e contains \p mod +bool contains_mod(expr *e, ast_manager &m) { + contains_mod_ns::contains_mod_proc t(m); + try { + for_each_expr(t, e); + } catch (const contains_mod_ns::found &) { return true; } + + return false; +} +bool contains_mod(const expr_ref &e) { + return contains_mod(e.get(), e.get_manager()); +} + +namespace contains_real_ns { +struct found {}; +struct contains_real_proc { + ast_manager &m; + arith_util m_arith; + contains_real_proc(ast_manager &a_m) : m(a_m), m_arith(m) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (m_arith.is_real(n)) throw found(); + } +}; +} // namespace contains_real_ns + +// Returns true if \p e contains a real-valued sub-term +bool contains_real(expr *e, ast_manager &m) { + contains_real_ns::contains_real_proc t(m); + try { + for_each_expr(t, e); + return false; + } catch (const contains_real_ns::found &) { return true; } +} +bool contains_real(const expr_ref &e) { + return contains_real(e.get(), e.get_manager()); +} + +/// Returns true if the range of substitution \p s is numeric +bool is_numeric_sub(const substitution &s) { + ast_manager &m(s.get_manager()); + arith_util arith(m); + bv_util bv(m); + std::pair var; + expr_offset r; + for (unsigned i = 0, sz = s.get_num_bindings(); i < sz; ++i) { + s.get_binding(i, var, r); + if (!(bv.is_numeral(r.get_expr()) || arith.is_numeral(r.get_expr()))) + return false; + } + return true; +} + } // namespace spacer template class rewriter_tpl; template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 1c8c64f61..dbc3083a2 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -21,126 +21,163 @@ Revision History: #pragma once +#include "ast/arith_decl_plugin.h" +#include "ast/array_decl_plugin.h" #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/bv_decl_plugin.h" +#include "ast/expr_map.h" +#include "model/model.h" #include "util/obj_hashtable.h" #include "util/ref_vector.h" #include "util/trace.h" #include "util/vector.h" -#include "ast/arith_decl_plugin.h" -#include "ast/array_decl_plugin.h" -#include "ast/bv_decl_plugin.h" -#include "ast/ast_util.h" -#include "ast/expr_map.h" -#include "model/model.h" -#include "util/stopwatch.h" #include "muz/spacer/spacer_antiunify.h" +#include "util/stopwatch.h" class model; class model_core; namespace spacer { - inline unsigned infty_level () { - return UINT_MAX; - } +inline unsigned infty_level() { return UINT_MAX; } - inline bool is_infty_level(unsigned lvl) { - // XXX: level is 16 bits in class pob - return lvl >= 65535; - } - - inline unsigned next_level(unsigned lvl) { - return is_infty_level(lvl)?lvl:(lvl+1); - } - - inline unsigned prev_level (unsigned lvl) { - if (is_infty_level(lvl)) return infty_level(); - if (lvl == 0) return 0; - return lvl - 1; - } - - struct pp_level { - unsigned m_level; - pp_level(unsigned l): m_level(l) {} - }; - - inline std::ostream& operator<<(std::ostream& out, pp_level const& p) { - if (is_infty_level(p.m_level)) { - return out << "oo"; - } else { - return out << p.m_level; - } - } - - typedef ptr_vector app_vector; - typedef ptr_vector decl_vector; - typedef obj_hashtable func_decl_set; - - /** - \brief hoist non-boolean if expressions. - */ - - void to_mbp_benchmark(std::ostream &out, const expr* fml, const app_ref_vector &vars); - - - // TBD: deprecate by qe::mbp - /** - * do the following in sequence - * 1. use qe_lite to cheaply eliminate vars - * 2. for remaining boolean vars, substitute using M - * 3. use MBP for remaining array and arith variables - * 4. for any remaining arith variables, substitute using M - */ - void qe_project (ast_manager& m, app_ref_vector& vars, - expr_ref& fml, model &mdl, - bool reduce_all_selects=false, - bool native_mbp=false, - bool dont_sub=false); - - // deprecate - void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model_ref& M, expr_map& map); - - // TBD: sort out - void expand_literals(ast_manager &m, expr_ref_vector& conjs); - expr_ref_vector compute_implicant_literals(model &mdl, - expr_ref_vector &formula); - void simplify_bounds (expr_ref_vector &lemmas); - void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); - - /** - * Ground expression by replacing all free variables by skolem - * constants. On return, out is the resulting expression, and vars is - * a map from variable ids to corresponding skolem constants. - */ - void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars); - - void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); - - bool contains_selects (expr* fml, ast_manager& m); - void get_select_indices (expr* fml, app_ref_vector& indices); - - void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix); - - /** - * extended pretty-printer - * used for debugging - * disables aliasing of common sub-expressions - */ - struct mk_epp : public mk_pp { - params_ref m_epp_params; - expr_ref m_epp_expr; - mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr); - void rw(expr *e, expr_ref &out); - }; - - bool is_clause(ast_manager &m, expr *n); - bool is_literal(ast_manager &m, expr *n); - bool is_atom(ast_manager &m, expr *n); - - // set f to true in model - void set_true_in_mdl(model &model, func_decl *f); +inline bool is_infty_level(unsigned lvl) { + // XXX: level is 16 bits in class pob + return lvl >= 65535; } +inline unsigned next_level(unsigned lvl) { + return is_infty_level(lvl) ? lvl : (lvl + 1); +} + +inline unsigned prev_level(unsigned lvl) { + if (is_infty_level(lvl)) return infty_level(); + if (lvl == 0) return 0; + return lvl - 1; +} + +struct pp_level { + unsigned m_level; + pp_level(unsigned l) : m_level(l) {} +}; + +inline std::ostream &operator<<(std::ostream &out, pp_level const &p) { + if (is_infty_level(p.m_level)) { + return out << "oo"; + } else { + return out << p.m_level; + } +} + +typedef ptr_vector app_vector; +typedef ptr_vector decl_vector; +typedef obj_hashtable func_decl_set; + +/** + \brief hoist non-boolean if expressions. +*/ + +void to_mbp_benchmark(std::ostream &out, const expr *fml, + const app_ref_vector &vars); + +// TBD: deprecate by qe::mbp +/** + * do the following in sequence + * 1. use qe_lite to cheaply eliminate vars + * 2. for remaining boolean vars, substitute using M + * 3. use MBP for remaining array and arith variables + * 4. for any remaining arith variables, substitute using M + */ +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects = false, bool native_mbp = false, + bool dont_sub = false); + +// deprecate +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model_ref &M, expr_map &map); + +// TBD: sort out +void expand_literals(ast_manager &m, expr_ref_vector &conjs); +expr_ref_vector compute_implicant_literals(model &mdl, + expr_ref_vector &formula); +void simplify_bounds(expr_ref_vector &lemmas); +bool is_normalized(expr_ref e, bool use_simplify_bounds = true, + bool factor_eqs = false); + +void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, + bool factor_eqs = false); + +void normalize_order(expr *e, expr_ref &out); +/** + * Ground expression by replacing all free variables by skolem + * constants. On return, out is the resulting expression, and vars is + * a map from variable ids to corresponding skolem constants. + */ +void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars); + +void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); + +bool contains_selects(expr *fml, ast_manager &m); +void get_select_indices(expr *fml, app_ref_vector &indices); + +void find_decls(expr *fml, app_ref_vector &decls, std::string &prefix); + +/** + * extended pretty-printer + * used for debugging + * disables aliasing of common sub-expressions + */ +struct mk_epp : public mk_pp { + params_ref m_epp_params; + expr_ref m_epp_expr; + mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, + char const *var_prefix = nullptr); + void rw(expr *e, expr_ref &out); +}; + +bool is_clause(ast_manager &m, expr *n); +bool is_literal(ast_manager &m, expr *n); +bool is_atom(ast_manager &m, expr *n); + +// set f to true in model +void set_true_in_mdl(model &model, func_decl *f); +/// Returns number of free variables in \p e +unsigned get_num_vars(expr *e); +// Return all uninterpreted constants of \p q +void collect_uninterp_consts(expr *a, expr_ref_vector &out); +bool has_nonlinear_mul(expr *e, ast_manager &m); + +// Returns true if \p e contains a multiplication a variable by not-a-number +bool has_nonlinear_var_mul(expr *e, ast_manager &m); + +// check whether lit is an instance of mono_var_pattern +bool is_mono_var(expr *lit, ast_manager &m, arith_util &a_util); + +// a mono_var_pattern has only one variable in the whole expression and is +// linear. lit is the literal with the variable +bool find_unique_mono_var_lit(const expr_ref &p, expr_ref &lit); + +/// Drop all literals that numerically match \p lit, from \p fml_vec. +/// +/// \p abs_fml holds the result. Returns true if any literal has been dropped +bool filter_out_lit(const expr_ref_vector &in, const expr_ref &lit, + expr_ref_vector &out); + +/// Returns true if range of s is numeric +bool is_numeric_sub(const substitution &s); + +// Returns true if \p e contains \p mod +bool contains_mod(const expr_ref &e); + +// Returns true if \p e contains a real-valued sub-term +bool contains_real(const expr_ref &e); + +// multiply fml with num and simplify rationals to ints +// fml should be in LIA/LRA/Arrays +// assumes that fml is a sum of products +void mul_by_rat(expr_ref &fml, rational num); + +} // namespace spacer diff --git a/src/muz/transforms/dl_mk_slice.cpp b/src/muz/transforms/dl_mk_slice.cpp index eda00b64d..834bb41ef 100644 --- a/src/muz/transforms/dl_mk_slice.cpp +++ b/src/muz/transforms/dl_mk_slice.cpp @@ -25,7 +25,7 @@ Revision History: Let x_i, y_i, z_i be indices into the vectors x, y, z. Suppose that positions in P and R are annotated with what is - slicable. + sliceable. Sufficient conditions for sliceability: @@ -43,9 +43,9 @@ Revision History: and the positions where z_i is used in P and R are sliceable - A more refined approach may be using Gaussean elimination based + A more refined approach may be using Gaussian elimination based on x,z and eliminating variables from x,y (expressing them in terms - of a disjoint subeset of x,z). + of a disjoint subset of x,z). --*/ @@ -441,7 +441,7 @@ namespace datalog { void mk_slice::filter_unique_vars(rule& r) { // - // Variables that occur in multiple uinterpreted predicates are not sliceable. + // Variables that occur in multiple uninterpreted predicates are not sliceable. // uint_set used_vars; for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { diff --git a/src/nlsat/nlsat_evaluator.cpp b/src/nlsat/nlsat_evaluator.cpp index f7b8f2797..97e0e3d72 100644 --- a/src/nlsat/nlsat_evaluator.cpp +++ b/src/nlsat/nlsat_evaluator.cpp @@ -286,22 +286,23 @@ namespace nlsat { } bool check_invariant() const { - SASSERT(m_sections.size() == m_sorted_sections.size()); - for (unsigned i = 0; i < m_sorted_sections.size(); i++) { - SASSERT(m_sorted_sections[i] < m_sections.size()); - SASSERT(m_sections[m_sorted_sections[i]].m_pos == i); - } - unsigned total_num_sections = 0; - unsigned total_num_signs = 0; - for (unsigned i = 0; i < m_info.size(); i++) { - SASSERT(m_info[i].m_first_section <= m_poly_sections.size()); - SASSERT(m_info[i].m_num_roots == 0 || m_info[i].m_first_section < m_poly_sections.size()); - SASSERT(m_info[i].m_first_sign < m_poly_signs.size()); - total_num_sections += m_info[i].m_num_roots; - total_num_signs += m_info[i].m_num_roots + 1; - } - SASSERT(total_num_sections == m_poly_sections.size()); - SASSERT(total_num_signs == m_poly_signs.size()); + DEBUG_CODE( + SASSERT(m_sections.size() == m_sorted_sections.size()); + for (unsigned i = 0; i < m_sorted_sections.size(); i++) { + SASSERT(m_sorted_sections[i] < m_sections.size()); + SASSERT(m_sections[m_sorted_sections[i]].m_pos == i); + } + unsigned total_num_sections = 0; + unsigned total_num_signs = 0; + for (unsigned i = 0; i < m_info.size(); i++) { + SASSERT(m_info[i].m_first_section <= m_poly_sections.size()); + SASSERT(m_info[i].m_num_roots == 0 || m_info[i].m_first_section < m_poly_sections.size()); + SASSERT(m_info[i].m_first_sign < m_poly_signs.size()); + total_num_sections += m_info[i].m_num_roots; + total_num_signs += m_info[i].m_num_roots + 1; + } + SASSERT(total_num_sections == m_poly_sections.size()); + SASSERT(total_num_signs == m_poly_signs.size());); return true; } diff --git a/src/opt/maxcore.cpp b/src/opt/maxcore.cpp index 1789f0675..d29da86f7 100644 --- a/src/opt/maxcore.cpp +++ b/src/opt/maxcore.cpp @@ -12,8 +12,8 @@ Abstract: - mu: max-sat algorithm by Nina and Bacchus, AAAI 2014. - 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: implementation of rc2 heuristic using cardinality constraints + - rc2t: implementation 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. @@ -27,7 +27,7 @@ Abstract: constraints the approach works like max-res. Given a (maximal) satisfying subset of the soft constraints the approach updates the upper bound if the current assignment - improves the current best assignmet. + improves the current best assignment. Furthermore, take the soft constraints that are complements to the current satisfying subset. E.g, if F are the hard constraints and @@ -44,7 +44,7 @@ Abstract: If k of these soft clauses are false in the satisfying assignment for the updated F, then k of the original soft clauses are also false under the assignment. - In summary: any assignment to the new clauses that satsfies F has the + In summary: any assignment to the new clauses that satisfies F has the same cost. Claim: If there are no satisfying assignments to F, then the current best assignment diff --git a/src/opt/maxsmt.h b/src/opt/maxsmt.h index 72195467f..ac39d7891 100644 --- a/src/opt/maxsmt.h +++ b/src/opt/maxsmt.h @@ -42,7 +42,7 @@ namespace opt { class maxsmt_solver { public: - virtual ~maxsmt_solver() {} + virtual ~maxsmt_solver() = default; virtual lbool operator()() = 0; virtual rational get_lower() const = 0; virtual rational get_upper() const = 0; diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index 8c4ec6ed9..bc41e0c6d 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -697,10 +697,25 @@ namespace opt { } } + /** + * Set the solver to the SAT core. + * It requres: + * - either EUF is enabled or the query is finite domain. + * - it is a MaxSAT query because linear optimiation is not exposed over the EUF core. + * - opt_solver relies on features from the legacy core. + * - the MaxSAT engine does not depend on old core features (branch and bound solver for MaxSAT) + * - proofs are not enabled + * Relaxation of these filters are possible by adding functionality to the new core. + * - Pareto optimizaiton might already be possible with EUF = true + * - optsmt needs to be disetangled from the legacy core + */ void context::update_solver() { sat_params p(m_params); if (!p.euf() && (!m_enable_sat || !probe_fd())) return; + + if (!is_maxsat_query()) + return; if (m_maxsat_engine != symbol("maxres") && m_maxsat_engine != symbol("rc2") && @@ -755,24 +770,29 @@ namespace opt { } }; + bool context::is_maxsat_query() { + for (objective& obj : m_objectives) + if (obj.m_type != O_MAXSMT) + return false; + return true; + } + bool context::probe_fd() { expr_fast_mark1 visited; is_fd proc(m); - try { + if (!is_maxsat_query()) + return false; + try { for (objective& obj : m_objectives) { - if (obj.m_type != O_MAXSMT) return false; maxsmt& ms = *m_maxsmts.find(obj.m_id); - for (unsigned j = 0; j < ms.size(); ++j) { + for (unsigned j = 0; j < ms.size(); ++j) quick_for_each_expr(proc, visited, ms[j]); - } } unsigned sz = get_solver().get_num_assertions(); - for (unsigned i = 0; i < sz; i++) { + for (unsigned i = 0; i < sz; i++) quick_for_each_expr(proc, visited, get_solver().get_assertion(i)); - } - for (expr* f : m_hard_constraints) { + for (expr* f : m_hard_constraints) quick_for_each_expr(proc, visited, f); - } } catch (const is_fd::found_fd &) { return false; diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index 49cc6adcd..8b0e8eab1 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -330,6 +330,7 @@ namespace opt { struct is_fd; bool probe_fd(); + bool is_maxsat_query(); struct is_propositional_fn; bool is_propositional(expr* e); diff --git a/src/opt/opt_lns.h b/src/opt/opt_lns.h index 8e16ec5c8..1bc27f9b1 100644 --- a/src/opt/opt_lns.h +++ b/src/opt/opt_lns.h @@ -28,7 +28,7 @@ namespace opt { class lns_context { public: - virtual ~lns_context() {} + virtual ~lns_context() = default; virtual void update_model(model_ref& mdl) = 0; virtual void relax_cores(vector const& cores) = 0; virtual rational cost(model& mdl) = 0; diff --git a/src/opt/opt_pareto.h b/src/opt/opt_pareto.h index ab5a90237..105f4007f 100644 --- a/src/opt/opt_pareto.h +++ b/src/opt/opt_pareto.h @@ -52,7 +52,7 @@ namespace opt { m_solver(s), m_params(p) { } - virtual ~pareto_base() {} + virtual ~pareto_base() = default; virtual void updt_params(params_ref & p) { m_solver->updt_params(p); m_params.copy(p); diff --git a/src/opt/opt_sls_solver.h b/src/opt/opt_sls_solver.h index 5256dd47a..2867f52a3 100644 --- a/src/opt/opt_sls_solver.h +++ b/src/opt/opt_sls_solver.h @@ -55,7 +55,7 @@ namespace opt { { updt_params(p); } - virtual ~sls_solver() {} + virtual ~sls_solver() = default; virtual void updt_params(params_ref & p) { m_solver->updt_params(p); diff --git a/src/params/CMakeLists.txt b/src/params/CMakeLists.txt index ca6268157..f420ddd6d 100644 --- a/src/params/CMakeLists.txt +++ b/src/params/CMakeLists.txt @@ -15,6 +15,7 @@ z3_add_component(params poly_rewriter_params.pyg rewriter_params.pyg seq_rewriter_params.pyg + solver_params.pyg EXTRA_REGISTER_MODULE_HEADERS context_params.h ) diff --git a/src/solver/solver_params.pyg b/src/params/solver_params.pyg similarity index 59% rename from src/solver/solver_params.pyg rename to src/params/solver_params.pyg index 21d0ab530..b475d368d 100644 --- a/src/solver/solver_params.pyg +++ b/src/params/solver_params.pyg @@ -5,5 +5,8 @@ def_module_params('solver', params=(('smtlib2_log', SYMBOL, '', "file to save solver interaction"), ('cancel_backup_file', SYMBOL, '', "file to save partial search state if search is canceled"), ('timeout', UINT, UINT_MAX, "timeout on the solver object; overwrites a global timeout"), + ('lemmas2console', BOOL, False, 'print lemmas during search'), + ('instantiations2console', BOOL, False, 'print quantifier instantiations to the console'), + ('axioms2files', BOOL, False, 'print negated theory axioms to separate files during search'), )) diff --git a/src/qe/mbp/mbp_arith.cpp b/src/qe/mbp/mbp_arith.cpp index 377b62ac0..a886a5a70 100644 --- a/src/qe/mbp/mbp_arith.cpp +++ b/src/qe/mbp/mbp_arith.cpp @@ -23,7 +23,6 @@ Revision History: #include "ast/ast_util.h" #include "ast/arith_decl_plugin.h" #include "ast/ast_pp.h" -#include "ast/rewriter/th_rewriter.h" #include "ast/expr_functors.h" #include "ast/rewriter/expr_safe_replace.h" #include "math/simplex/model_based_opt.h" @@ -32,13 +31,13 @@ Revision History: #include "model/model_v2_pp.h" namespace mbp { - + struct arith_project_plugin::imp { - ast_manager& m; + ast_manager& m; arith_util a; - bool m_check_purified { true }; // check that variables are properly pure - bool m_apply_projection { false }; + bool m_check_purified = true; // check that variables are properly pure + bool m_apply_projection = false; imp(ast_manager& m) : @@ -48,10 +47,10 @@ namespace mbp { void insert_mul(expr* x, rational const& v, obj_map& ts) { rational w; - if (ts.find(x, w)) - ts.insert(x, w + v); - else - ts.insert(x, v); + if (ts.find(x, w)) + ts.insert(x, w + v); + else + ts.insert(x, v); } @@ -65,15 +64,17 @@ namespace mbp { rational c(0), mul(1); expr_ref t(m); opt::ineq_type ty = opt::t_le; - expr* e1, *e2; - DEBUG_CODE(expr_ref val(m); - eval(lit, val); - CTRACE("qe", !m.is_true(val), tout << mk_pp(lit, m) << " := " << val << "\n";); - SASSERT(m.limit().is_canceled() || !m.is_false(val));); - - if (!m.inc()) + expr* e1, * e2; + DEBUG_CODE(expr_ref val(m); + eval(lit, val); + CTRACE("qe", !m.is_true(val), tout << mk_pp(lit, m) << " := " << val << "\n";); + if (m.is_false(val)) return false; - + SASSERT(m.limit().is_canceled() || !m.is_false(val));); + + if (!m.inc()) + return false; + TRACE("opt", tout << mk_pp(lit, m) << " " << a.is_lt(lit) << " " << a.is_gt(lit) << "\n";); bool is_not = m.is_not(lit, lit); if (is_not) { @@ -86,37 +87,35 @@ namespace mbp { ty = is_not ? opt::t_lt : opt::t_le; } else if ((a.is_lt(lit, e1, e2) || a.is_gt(lit, e2, e1))) { - linearize(mbo, eval, mul, e1, c, fmls, ts, tids); + linearize(mbo, eval, mul, e1, c, fmls, ts, tids); linearize(mbo, eval, -mul, e2, c, fmls, ts, tids); - ty = is_not ? opt::t_le: opt::t_lt; + ty = is_not ? opt::t_le : opt::t_lt; } else if (m.is_eq(lit, e1, e2) && !is_not && is_arith(e1)) { - linearize(mbo, eval, mul, e1, c, fmls, ts, tids); + linearize(mbo, eval, mul, e1, c, fmls, ts, tids); linearize(mbo, eval, -mul, e2, c, fmls, ts, tids); ty = opt::t_eq; - } + } else if (m.is_eq(lit, e1, e2) && is_not && is_arith(e1)) { - + rational r1, r2; - expr_ref val1 = eval(e1); + expr_ref val1 = eval(e1); expr_ref val2 = eval(e2); - //TRACE("qe", tout << mk_pp(e1, m) << " " << val1 << "\n";); - //TRACE("qe", tout << mk_pp(e2, m) << " " << val2 << "\n";); if (!a.is_numeral(val1, r1)) return false; if (!a.is_numeral(val2, r2)) return false; SASSERT(r1 != r2); if (r1 < r2) { std::swap(e1, e2); - } + } ty = opt::t_lt; - linearize(mbo, eval, mul, e1, c, fmls, ts, tids); - linearize(mbo, eval, -mul, e2, c, fmls, ts, tids); - } + linearize(mbo, eval, mul, e1, c, fmls, ts, tids); + linearize(mbo, eval, -mul, e2, c, fmls, ts, tids); + } else if (m.is_distinct(lit) && !is_not && is_arith(to_app(lit)->get_arg(0))) { expr_ref val(m); rational r; app* alit = to_app(lit); - vector > nums; + vector > nums; for (expr* arg : *alit) { val = eval(arg); TRACE("qe", tout << mk_pp(arg, m) << " " << val << "\n";); @@ -125,8 +124,8 @@ namespace mbp { } std::sort(nums.begin(), nums.end(), compare_second()); for (unsigned i = 0; i + 1 < nums.size(); ++i) { - SASSERT(nums[i].second < nums[i+1].second); - expr_ref fml(a.mk_lt(nums[i].first, nums[i+1].first), m); + SASSERT(nums[i].second < nums[i + 1].second); + expr_ref fml(a.mk_lt(nums[i].first, nums[i + 1].first), m); if (!linearize(mbo, eval, fml, fmls, tids)) { return false; } @@ -139,14 +138,14 @@ namespace mbp { map values; bool found_eq = false; for (unsigned i = 0; !found_eq && i < to_app(lit)->get_num_args(); ++i) { - expr* arg1 = to_app(lit)->get_arg(i), *arg2 = nullptr; + expr* arg1 = to_app(lit)->get_arg(i), * arg2 = nullptr; rational r; expr_ref val = eval(arg1); TRACE("qe", tout << mk_pp(arg1, m) << " " << val << "\n";); if (!a.is_numeral(val, r)) return false; if (values.find(r, arg2)) { ty = opt::t_eq; - linearize(mbo, eval, mul, arg1, c, fmls, ts, tids); + linearize(mbo, eval, mul, arg1, c, fmls, ts, tids); linearize(mbo, eval, -mul, arg2, c, fmls, ts, tids); found_eq = true; } @@ -169,27 +168,39 @@ namespace mbp { // // convert linear arithmetic term into an inequality for mbo. // - void linearize(opt::model_based_opt& mbo, model_evaluator& eval, rational const& mul, expr* t, rational& c, - expr_ref_vector& fmls, obj_map& ts, obj_map& tids) { - expr* t1, *t2, *t3; + void linearize(opt::model_based_opt& mbo, model_evaluator& eval, rational const& mul, expr* t, rational& c, + expr_ref_vector& fmls, obj_map& ts, obj_map& tids) { + expr* t1, * t2, * t3; rational mul1; expr_ref val(m); - if (a.is_mul(t, t1, t2) && is_numeral(t1, mul1)) - linearize(mbo, eval, mul* mul1, t2, c, fmls, ts, tids); - else if (a.is_mul(t, t1, t2) && is_numeral(t2, mul1)) - linearize(mbo, eval, mul* mul1, t1, c, fmls, ts, tids); + + auto add_def = [&](expr* t1, rational const& m, vars& coeffs) { + obj_map ts0; + rational mul0(1), c0(0); + linearize(mbo, eval, mul0, t1, c0, fmls, ts0, tids); + extract_coefficients(mbo, eval, ts0, tids, coeffs); + insert_mul(t, mul, ts); + return c0; + }; + + if (tids.contains(t)) + insert_mul(t, mul, ts); + else if (a.is_mul(t, t1, t2) && is_numeral(t1, mul1)) + linearize(mbo, eval, mul * mul1, t2, c, fmls, ts, tids); + else if (a.is_mul(t, t1, t2) && is_numeral(t2, mul1)) + linearize(mbo, eval, mul * mul1, t1, c, fmls, ts, tids); else if (a.is_uminus(t, t1)) linearize(mbo, eval, -mul, t1, c, fmls, ts, tids); - else if (a.is_numeral(t, mul1)) - c += mul * mul1; + else if (a.is_numeral(t, mul1)) + c += mul * mul1; else if (a.is_add(t)) { - for (expr* arg : *to_app(t)) - linearize(mbo, eval, mul, arg, c, fmls, ts, tids); + for (expr* arg : *to_app(t)) + linearize(mbo, eval, mul, arg, c, fmls, ts, tids); } else if (a.is_sub(t, t1, t2)) { - linearize(mbo, eval, mul, t1, c, fmls, ts, tids); + linearize(mbo, eval, mul, t1, c, fmls, ts, tids); linearize(mbo, eval, -mul, t2, c, fmls, ts, tids); - } + } else if (m.is_ite(t, t1, t2, t3)) { val = eval(t1); @@ -204,17 +215,32 @@ namespace mbp { linearize(mbo, eval, mul, t3, c, fmls, ts, tids); } else { + IF_VERBOSE(1, verbose_stream() << "mbp failed on if: " << mk_pp(t, m) << " := " << val << "\n"); throw default_exception("mbp evaluation didn't produce a truth value"); } } + else if (a.is_mod(t, t1, t2) && is_numeral(t2, mul1) && mul1 > 0) { + // v = t1 mod mul1 + vars coeffs; + rational c0 = add_def(t1, mul1, coeffs); + tids.insert(t, mbo.add_mod(coeffs, c0, mul1)); + + } + else if (a.is_idiv(t, t1, t2) && is_numeral(t2, mul1) && mul1 > 0) { + // v = t1 div mul1 + vars coeffs; + rational c0 = add_def(t1, mul1, coeffs); + tids.insert(t, mbo.add_div(coeffs, c0, mul1)); + } else if (a.is_mod(t, t1, t2) && is_numeral(t2, mul1) && !mul1.is_zero()) { rational r; val = eval(t); if (!a.is_numeral(val, r)) { + IF_VERBOSE(1, verbose_stream() << "mbp failed on " << mk_pp(t, m) << " := " << val << "\n"); throw default_exception("mbp evaluation didn't produce an integer"); } - c += mul*r; - // t1 mod mul1 == r + c += mul * r; + rational c0(-r), mul0(1); obj_map ts0; linearize(mbo, eval, mul0, t1, c0, fmls, ts0, tids); @@ -222,9 +248,8 @@ namespace mbp { extract_coefficients(mbo, eval, ts0, tids, coeffs); mbo.add_divides(coeffs, c0, mul1); } - else { + else insert_mul(t, mul, ts); - } } bool is_numeral(expr* t, rational& r) { @@ -232,8 +257,8 @@ namespace mbp { } struct compare_second { - bool operator()(std::pair const& a, - std::pair const& b) const { + bool operator()(std::pair const& a, + std::pair const& b) const { return a.second < b.second; } }; @@ -243,14 +268,14 @@ namespace mbp { } rational n_sign(rational const& b) { - return rational(b.is_pos()?-1:1); + return rational(b.is_pos() ? -1 : 1); } bool operator()(model& model, app* v, app_ref_vector& vars, expr_ref_vector& lits) { app_ref_vector vs(m); vs.push_back(v); vector defs; - return project(model, vs, lits, defs, false) && vs.empty(); + return project(model, vs, lits, defs, false) && vs.empty(); } typedef opt::model_based_opt::var var; @@ -259,21 +284,21 @@ namespace mbp { expr_ref var2expr(ptr_vector const& index2expr, var const& v) { expr_ref t(index2expr[v.m_id], m); - if (!v.m_coeff.is_one()) { - t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); - } + if (!v.m_coeff.is_one()) + t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); return t; } bool project(model& model, app_ref_vector& vars, expr_ref_vector& fmls, vector& result, bool compute_def) { bool has_arith = false; - for (expr* v : vars) - has_arith |= is_arith(v); - if (!has_arith) - return true; + for (expr* v : vars) + has_arith |= is_arith(v); + if (!has_arith) + return true; model_evaluator eval(model); TRACE("qe", tout << model;); eval.set_model_completion(true); + model.set_inline(); compute_def |= m_apply_projection; opt::model_based_opt mbo; @@ -281,7 +306,7 @@ namespace mbp { expr_ref_vector pinned(m); unsigned j = 0; TRACE("qe", tout << "vars: " << vars << "\n"; - for (expr* f : fmls) tout << mk_pp(f, m) << " := " << model(f) << "\n";); + for (expr* f : fmls) tout << mk_pp(f, m) << " := " << model(f) << "\n";); for (unsigned i = 0; i < fmls.size(); ++i) { expr* fml = fmls.get(i); if (!linearize(mbo, eval, fml, fmls, tids)) { @@ -294,8 +319,8 @@ namespace mbp { } fmls.shrink(j); TRACE("qe", tout << "formulas\n" << fmls << "\n"; - for (auto const& [e, id] : tids) - tout << mk_pp(e, m) << " -> " << id << "\n";); + for (auto const& [e, id] : tids) + tout << mk_pp(e, m) << " -> " << id << "\n";); // fmls holds residue, // mbo holds linear inequalities that are in scope @@ -306,75 +331,87 @@ namespace mbp { // return those to fmls. expr_mark var_mark, fmls_mark; - for (app * v : vars) { + for (app* v : vars) { var_mark.mark(v); if (is_arith(v) && !tids.contains(v)) { rational r; expr_ref val = eval(v); if (!m.inc()) return false; - if (!a.is_numeral(val, r)) + if (!a.is_numeral(val, r)) { + IF_VERBOSE(1, verbose_stream() << "mbp failed on " << mk_pp(v, m) << " := " << val << "\n"); throw default_exception("evaluation did not produce a numeral"); + } TRACE("qe", tout << mk_pp(v, m) << " " << val << "\n";); tids.insert(v, mbo.add_var(r, a.is_int(v))); } } // bail on variables in non-linear sub-terms + auto is_pure = [&](expr* e) { + expr* x, * y; + rational r; + if (a.is_mod(e, x, y) && a.is_numeral(y)) + return true; + if (a.is_idiv(e, x, y) && a.is_numeral(y, r) && r > 0) + return true; + return false; + }; + for (auto& kv : tids) { expr* e = kv.m_key; - if (is_arith(e) && !var_mark.is_marked(e)) - mark_rec(fmls_mark, e); + if (is_arith(e) && !is_pure(e) && !var_mark.is_marked(e)) + mark_rec(fmls_mark, e); } if (m_check_purified) { - for (expr* fml : fmls) - mark_rec(fmls_mark, fml); + for (expr* fml : fmls) + mark_rec(fmls_mark, fml); for (auto& kv : tids) { expr* e = kv.m_key; - if (!var_mark.is_marked(e)) - mark_rec(fmls_mark, e); + if (!var_mark.is_marked(e) && !is_pure(e)) + mark_rec(fmls_mark, e); } } ptr_vector index2expr; - for (auto& kv : tids) - index2expr.setx(kv.m_value, kv.m_key, nullptr); + for (auto& kv : tids) + index2expr.setx(kv.m_value, kv.m_key, nullptr); j = 0; unsigned_vector real_vars; for (app* v : vars) { - if (is_arith(v) && !fmls_mark.is_marked(v)) - real_vars.push_back(tids.find(v)); - else - vars[j++] = v; + if (is_arith(v) && !fmls_mark.is_marked(v)) + real_vars.push_back(tids.find(v)); + else + vars[j++] = v; } vars.shrink(j); - - TRACE("qe", tout << "remaining vars: " << vars << "\n"; - for (unsigned v : real_vars) tout << "v" << v << " " << mk_pp(index2expr[v], m) << "\n"; - mbo.display(tout);); + + TRACE("qe", tout << "remaining vars: " << vars << "\n"; + for (unsigned v : real_vars) tout << "v" << v << " " << mk_pp(index2expr[v], m) << "\n"; + mbo.display(tout);); vector defs = mbo.project(real_vars.size(), real_vars.data(), compute_def); vector rows; mbo.get_live_rows(rows); rows2fmls(rows, index2expr, fmls); TRACE("qe", mbo.display(tout << "mbo result\n"); - for (auto const& d : defs) tout << "def: " << d << "\n"; - tout << fmls << "\n";); - - if (compute_def) - optdefs2mbpdef(defs, index2expr, real_vars, result); + for (auto const& d : defs) tout << "def: " << d << "\n"; + tout << fmls << "\n";); + + if (compute_def) + optdefs2mbpdef(defs, index2expr, real_vars, result); if (m_apply_projection && !apply_projection(eval, result, fmls)) return false; TRACE("qe", for (auto const& [v, t] : result) tout << v << " := " << t << "\n"; - for (auto* f : fmls) - tout << mk_pp(f, m) << " := " << eval(f) << "\n"; - tout << "fmls:" << fmls << "\n";); + for (auto* f : fmls) + tout << mk_pp(f, m) << " := " << eval(f) << "\n"; + tout << "fmls:" << fmls << "\n";); return true; - } + } void optdefs2mbpdef(vector const& defs, ptr_vector const& index2expr, unsigned_vector const& real_vars, vector& result) { SASSERT(defs.size() == real_vars.size()); @@ -384,31 +421,93 @@ namespace mbp { bool is_int = a.is_int(x); expr_ref_vector ts(m); expr_ref t(m); - for (var const& v : d.m_vars) - ts.push_back(var2expr(index2expr, v)); + for (var const& v : d.m_vars) + ts.push_back(var2expr(index2expr, v)); if (!d.m_coeff.is_zero()) ts.push_back(a.mk_numeral(d.m_coeff, is_int)); if (ts.empty()) ts.push_back(a.mk_numeral(rational(0), is_int)); t = mk_add(ts); - if (!d.m_div.is_one() && is_int) - t = a.mk_idiv(t, a.mk_numeral(d.m_div, is_int)); - else if (!d.m_div.is_one() && !is_int) - t = a.mk_div(t, a.mk_numeral(d.m_div, is_int)); + if (!d.m_div.is_one() && is_int) + t = a.mk_idiv(t, a.mk_numeral(d.m_div, is_int)); + else if (!d.m_div.is_one() && !is_int) + t = a.mk_div(t, a.mk_numeral(d.m_div, is_int)); result.push_back(def(expr_ref(x, m), t)); } } - void rows2fmls(vector const& rows, ptr_vector const& index2expr, expr_ref_vector& fmls) { - for (row const& r : rows) { - expr_ref_vector ts(m); - expr_ref t(m), s(m), val(m); - if (r.m_vars.empty()) { + expr_ref id2expr(u_map const& def_vars, ptr_vector const& index2expr, unsigned id) { + row r; + if (def_vars.find(id, r)) + return row2expr(def_vars, index2expr, r); + return expr_ref(index2expr[id], m); + } + + expr_ref row2expr(u_map const& def_vars, ptr_vector const& index2expr, row const& r) { + expr_ref_vector ts(m); + expr_ref t(m); + rational n; + for (var const& v : r.m_vars) { + t = id2expr(def_vars, index2expr, v.m_id); + if (a.is_numeral(t, n) && n == 0) continue; + else if (a.is_numeral(t, n)) + t = a.mk_numeral(v.m_coeff * n, a.is_int(t)); + else if (!v.m_coeff.is_one()) + t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); + ts.push_back(t); + } + switch (r.m_type) { + case opt::t_mod: + if (ts.empty()) { + t = a.mk_int(mod(r.m_coeff, r.m_mod)); + return t; } - if (r.m_vars.size() == 1 && r.m_vars[0].m_coeff.is_neg() && r.m_type != opt::t_mod) { + ts.push_back(a.mk_int(r.m_coeff)); + t = mk_add(ts); + t = a.mk_mod(t, a.mk_int(r.m_mod)); + return t; + case opt::t_div: + if (ts.empty()) { + t = a.mk_int(div(r.m_coeff, r.m_mod)); + return t; + } + ts.push_back(a.mk_int(r.m_coeff)); + t = mk_add(ts); + t = a.mk_idiv(t, a.mk_int(r.m_mod)); + return t; + case opt::t_divides: + ts.push_back(a.mk_int(r.m_coeff)); + return mk_add(ts); + default: + return mk_add(ts); + } + } + + void rows2fmls(vector const& rows, ptr_vector const& index2expr, expr_ref_vector& fmls) { + + u_map def_vars; + for (row const& r : rows) { + if (r.m_type == opt::t_mod) + def_vars.insert(r.m_id, r); + else if (r.m_type == opt::t_div) + def_vars.insert(r.m_id, r); + } + + for (row const& r : rows) { + expr_ref t(m), s(m), val(m); + + if (r.m_vars.empty()) + continue; + if (r.m_type == opt::t_mod) + continue; + if (r.m_type == opt::t_div) + continue; + + if (r.m_vars.size() == 1 && r.m_vars[0].m_coeff.is_neg() && + (r.m_type == opt::t_eq || r.m_type == opt::t_le || r.m_type == opt::t_lt)) { var const& v = r.m_vars[0]; - t = index2expr[v.m_id]; + t = id2expr(def_vars, index2expr, v.m_id); if (!v.m_coeff.is_minus_one()) { t = a.mk_mul(a.mk_numeral(-v.m_coeff, a.is_int(t)), t); } @@ -417,29 +516,22 @@ namespace mbp { case opt::t_lt: t = a.mk_gt(t, s); break; case opt::t_le: t = a.mk_ge(t, s); break; case opt::t_eq: t = a.mk_eq(t, s); break; - default: UNREACHABLE(); + default: UNREACHABLE(); break; } fmls.push_back(t); continue; } - for (var const& v : r.m_vars) { - t = index2expr[v.m_id]; - if (!v.m_coeff.is_one()) { - t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); - } - ts.push_back(t); - } - t = mk_add(ts); + t = row2expr(def_vars, index2expr, r); s = a.mk_numeral(-r.m_coeff, r.m_coeff.is_int() && a.is_int(t)); switch (r.m_type) { case opt::t_lt: t = a.mk_lt(t, s); break; case opt::t_le: t = a.mk_le(t, s); break; case opt::t_eq: t = a.mk_eq(t, s); break; - case opt::t_mod: - if (!r.m_coeff.is_zero()) { - t = a.mk_sub(t, s); - } - t = a.mk_eq(a.mk_mod(t, a.mk_numeral(r.m_mod, true)), a.mk_int(0)); + case opt::t_divides: + t = a.mk_eq(a.mk_mod(t, a.mk_int(r.m_mod)), a.mk_int(0)); + break; + default: + UNREACHABLE(); break; } fmls.push_back(t); @@ -468,11 +560,11 @@ namespace mbp { SASSERT(validate_model(eval, fmls0)); // extract linear constraints - - for (expr * fml : fmls) { + + for (expr* fml : fmls) { linearize(mbo, eval, fml, fmls, tids); } - + // find optimal value value = mbo.maximize(); @@ -536,6 +628,7 @@ namespace mbp { expr_ref val = eval(v); if (!a.is_numeral(val, r)) { TRACE("qe", tout << eval.get_model() << "\n";); + IF_VERBOSE(1, verbose_stream() << "mbp failed on " << mk_pp(v, m) << " := " << val << "\n"); throw default_exception("mbp evaluation was only partial"); } id = mbo.add_var(r, a.is_int(v)); @@ -543,7 +636,7 @@ namespace mbp { } CTRACE("qe", kv.m_value.is_zero(), tout << mk_pp(v, m) << " has coefficeint 0\n";); if (!kv.m_value.is_zero()) { - coeffs.push_back(var(id, kv.m_value)); + coeffs.push_back(var(id, kv.m_value)); } } } @@ -571,7 +664,7 @@ namespace mbp { }; - arith_project_plugin::arith_project_plugin(ast_manager& m):project_plugin(m) { + arith_project_plugin::arith_project_plugin(ast_manager& m) :project_plugin(m) { m_imp = alloc(imp, m); } diff --git a/src/qe/mbp/mbp_datatypes.cpp b/src/qe/mbp/mbp_datatypes.cpp index e40a19546..32e2b9f27 100644 --- a/src/qe/mbp/mbp_datatypes.cpp +++ b/src/qe/mbp/mbp_datatypes.cpp @@ -169,7 +169,7 @@ namespace mbp { eqs.push_back(m.mk_eq(access(c, j, acc, b), a->get_arg(j))); } } - if (!is_app_of(b, c)) { + if (!is_app_of(b, c) && dt.get_datatype_num_constructors(c->get_range()) != 1) { eqs.push_back(m.mk_app(rec, b)); } @@ -231,7 +231,7 @@ namespace mbp { } func_decl* c = to_app(l)->get_decl(); ptr_vector const& acc = *dt.get_constructor_accessors(c); - if (!is_app_of(r, c)) { + if (!is_app_of(r, c) && dt.get_datatype_num_constructors(c->get_range()) != 1) { lits.push_back(m.mk_app(dt.get_constructor_is(c), r)); } for (unsigned i = 0; i < acc.size(); ++i) { diff --git a/src/qe/mbp/mbp_plugin.cpp b/src/qe/mbp/mbp_plugin.cpp index 7731f803c..2584a4ce1 100644 --- a/src/qe/mbp/mbp_plugin.cpp +++ b/src/qe/mbp/mbp_plugin.cpp @@ -98,6 +98,7 @@ namespace mbp { bool project_plugin::reduce(model_evaluator& eval, model& model, expr* fml, expr_ref_vector& fmls) { expr* nfml, * f1, * f2, * f3; expr_ref val(m); + model.set_inline(); if (m.is_not(fml, nfml) && m.is_distinct(nfml)) push_back(fmls, pick_equality(m, model, nfml)); else if (m.is_or(fml)) { @@ -245,8 +246,10 @@ namespace mbp { bool project_plugin::is_true(model_evaluator& eval, expr* e) { expr_ref val = eval(e); bool tt = m.is_true(val); - if (!tt && !m.is_false(val) && contains_uninterpreted(val)) + if (!tt && !m.is_false(val) && contains_uninterpreted(val)) { + IF_VERBOSE(1, verbose_stream() << "mbp failed on " << mk_pp(e, m) << " := " << val << "\n"); throw default_exception("could not evaluate Boolean in model"); + } SASSERT(tt || m.is_false(val)); return tt; } diff --git a/src/qe/mbp/mbp_plugin.h b/src/qe/mbp/mbp_plugin.h index 12b592960..4f73b92e6 100644 --- a/src/qe/mbp/mbp_plugin.h +++ b/src/qe/mbp/mbp_plugin.h @@ -59,7 +59,7 @@ namespace mbp { public: project_plugin(ast_manager& m) :m(m), m_cache(m), m_args(m), m_pure_eqs(m) {} - virtual ~project_plugin() {} + virtual ~project_plugin() = default; virtual bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) { return false; } /** \brief partial solver. diff --git a/src/qe/mbp/mbp_solve_plugin.cpp b/src/qe/mbp/mbp_solve_plugin.cpp index c43fa402f..d80a3665d 100644 --- a/src/qe/mbp/mbp_solve_plugin.cpp +++ b/src/qe/mbp/mbp_solve_plugin.cpp @@ -376,7 +376,7 @@ namespace mbp { rhs = m_bv.mk_concat(2, args); } else if (lo > 0 && hi + 1 == sz) { - expr* args[3] = { rhs, m_bv.mk_extract(lo - 1, 0, e) }; + expr* args[2] = { rhs, m_bv.mk_extract(lo - 1, 0, e) }; rhs = m_bv.mk_concat(2, args); } else { @@ -384,6 +384,15 @@ namespace mbp { } return true; } +#if 0 + expr *f = nullptr + // this one is for Nuno to find and generalize for invertible expressions + if (m_bv.is_bv_add(lhs, e, f) && is_variable(e)) { + lhs = e; + rhs = m_bv.mk_bv_sub(rhs, f); + return true; + } +#endif return false; } public: diff --git a/src/qe/mbp/mbp_solve_plugin.h b/src/qe/mbp/mbp_solve_plugin.h index 27e7f85a8..2450eab4f 100644 --- a/src/qe/mbp/mbp_solve_plugin.h +++ b/src/qe/mbp/mbp_solve_plugin.h @@ -36,7 +36,7 @@ namespace mbp { bool is_variable(expr* e) const { return m_is_var(e); } public: solve_plugin(ast_manager& m, family_id fid, is_variable_proc& is_var) : m(m), m_id(fid), m_is_var(is_var) {} - virtual ~solve_plugin() {} + virtual ~solve_plugin() = default; family_id get_family_id() const { return m_id; } /// Process (and potentially augment) a literal expr_ref operator() (expr *lit); diff --git a/src/qe/nlarith_util.h b/src/qe/nlarith_util.h index acbe4889e..dccc4f606 100644 --- a/src/qe/nlarith_util.h +++ b/src/qe/nlarith_util.h @@ -116,7 +116,7 @@ namespace nlarith { class eval { public: - virtual ~eval() {} + virtual ~eval() = default; virtual lbool operator()(app* a) = 0; }; @@ -124,7 +124,7 @@ namespace nlarith { class branch { public: - virtual ~branch() {} + virtual ~branch() = default; virtual app* get_constraint() = 0; virtual void get_updates(ptr_vector& atoms, svector& updates) = 0; }; diff --git a/src/qe/qe.cpp b/src/qe/qe.cpp index 509b73835..d859880d8 100644 --- a/src/qe/qe.cpp +++ b/src/qe/qe.cpp @@ -877,7 +877,7 @@ namespace qe { class quant_elim { public: - virtual ~quant_elim() {} + virtual ~quant_elim() = default; virtual lbool eliminate_exists( unsigned num_vars, app* const* vars, diff --git a/src/qe/qe.h b/src/qe/qe.h index a5152522f..55729d0b5 100644 --- a/src/qe/qe.h +++ b/src/qe/qe.h @@ -149,7 +149,7 @@ namespace qe { m_ctx(ctx) {} - virtual ~qe_solver_plugin() {} + virtual ~qe_solver_plugin() = default; family_id get_family_id() { return m_fid; } diff --git a/src/qe/qe_mbi.h b/src/qe/qe_mbi.h index e35822813..70c0f98bb 100644 --- a/src/qe/qe_mbi.h +++ b/src/qe/qe_mbi.h @@ -43,7 +43,7 @@ namespace qe { lbool is_shared_cached(expr* e); public: mbi_plugin(ast_manager& m): m(m), m_shared_trail(m) {} - virtual ~mbi_plugin() {} + virtual ~mbi_plugin() = default; /** * Set the shared symbols. diff --git a/src/qe/qe_mbp.cpp b/src/qe/qe_mbp.cpp index efadc59e6..6b0e3cf32 100644 --- a/src/qe/qe_mbp.cpp +++ b/src/qe/qe_mbp.cpp @@ -269,8 +269,6 @@ public: } bool validate_model(model& model, expr_ref_vector const& fmls) { - expr_ref val(m); - model_evaluator eval(model); for (expr* f : fmls) { VERIFY(!model.is_false(f)); } diff --git a/src/qe/qsat.cpp b/src/qe/qsat.cpp index 767b96e5d..7e28437f6 100644 --- a/src/qe/qsat.cpp +++ b/src/qe/qsat.cpp @@ -241,9 +241,8 @@ namespace qe { while (sz0 != todo.size()) { app* a = to_app(todo.back()); todo.pop_back(); - if (mark.is_marked(a)) { + if (mark.is_marked(a)) continue; - } mark.mark(a); if (m_lit2pred.find(a, p)) { @@ -284,9 +283,8 @@ namespace qe { m_elevel.insert(r, l); eq = m.mk_eq(r, a); defs.push_back(eq); - if (!is_predicate(a, l.max())) { + if (!is_predicate(a, l.max())) insert(r, l); - } level.merge(l); } } @@ -358,18 +356,14 @@ namespace qe { expr *c, *d; max_level lvl2; TRACE("qe", tout << mk_pp(a, m) << " " << lvl << "\n";); - if (m_asm2pred.find(a, b)) { - q = b; - } - else if (m.is_not(a, c) && m_asm2pred.find(c, b)) { - q = m.mk_not(b); - } - else if (m_pred2asm.find(a, d)) { - q = a; - } - else if (m.is_not(a, c) && m_pred2asm.find(c, d)) { - q = a; - } + if (m_asm2pred.find(a, b)) + q = b; + else if (m.is_not(a, c) && m_asm2pred.find(c, b)) + q = m.mk_not(b); + else if (m_pred2asm.find(a, d)) + q = a; + else if (m.is_not(a, c) && m_pred2asm.find(c, d)) + q = a; else { p = fresh_bool("def"); if (m.is_not(a, a)) { @@ -427,12 +421,10 @@ namespace qe { } } if (args.size() == sz) { - if (diff) { - r = m.mk_app(a->get_decl(), args); - } - else { - r = to_app(a); - } + if (diff) + r = m.mk_app(a->get_decl(), args); + else + r = to_app(a); cache.insert(a, r); trail.push_back(r); todo.pop_back(); @@ -467,12 +459,10 @@ namespace qe { for (unsigned j = 0; j < m_preds[i].size(); ++j) { app* p = m_preds[i][j]; expr* e; - if (m_pred2lit.find(p, e)) { - out << mk_pp(p, m) << " := " << mk_pp(e, m) << "\n"; - } - else { - out << mk_pp(p, m) << "\n"; - } + if (m_pred2lit.find(p, e)) + out << mk_pp(p, m) << " := " << mk_pp(e, m) << "\n"; + else + out << mk_pp(p, m) << "\n"; } } } @@ -483,12 +473,10 @@ namespace qe { expr* e = a; bool is_not = m.is_not(a, e); out << mk_pp(a, m); - if (m_elevel.find(e, lvl)) { - lvl.display(out << " - "); - } - if (m_pred2lit.find(e, e)) { - out << " : " << (is_not?"!":"") << mk_pp(e, m); - } + if (m_elevel.find(e, lvl)) + lvl.display(out << " - "); + if (m_pred2lit.find(e, e)) + out << " : " << (is_not?"!":"") << mk_pp(e, m); out << "\n"; } } @@ -500,9 +488,8 @@ namespace qe { while (sz0 != todo.size()) { expr* e = todo.back(); todo.pop_back(); - if (mark.is_marked(e) || is_var(e)) { - continue; - } + if (mark.is_marked(e) || is_var(e)) + continue; mark.mark(e); if (is_quantifier(e)) { todo.push_back(to_quantifier(e)->get_expr()); @@ -510,12 +497,10 @@ namespace qe { } SASSERT(is_app(e)); app* a = to_app(e); - if (is_uninterp_const(a)) { // TBD generalize for uninterpreted functions. - vars.push_back(a); - } - for (expr* arg : *a) { - todo.push_back(arg); - } + if (is_uninterp_const(a)) // TBD generalize for uninterpreted functions. + vars.push_back(a); + for (expr* arg : *a) + todo.push_back(arg); } } @@ -637,57 +622,55 @@ namespace qe { check_cancel(); expr_ref_vector asms(m_asms); m_pred_abs.get_assumptions(m_model.get(), asms); - if (m_model.get()) { + if (m_model.get()) validate_assumptions(*m_model.get(), asms); - } TRACE("qe", tout << asms << "\n";); solver& s = get_kernel(m_level).s(); lbool res = s.check_sat(asms); switch (res) { case l_true: s.get_model(m_model); + CTRACE("qe", !m_model, tout << "no model\n"); if (!m_model) return l_undef; SASSERT(validate_defs("check_sat")); SASSERT(!m_model.get() || validate_assumptions(*m_model.get(), asms)); SASSERT(validate_model(asms)); TRACE("qe", s.display(tout); display(tout << "\n", *m_model.get()); display(tout, asms); ); - if (m_level == 0) { + if (m_level == 0) m_model_save = m_model; - } push(); - if (m_level == 1 && m_mode == qsat_maximize) { + if (m_level == 1 && m_mode == qsat_maximize) maximize_model(); - } break; case l_false: switch (m_level) { case 0: return l_false; case 1: - if (m_mode == qsat_sat) { + if (m_mode == qsat_sat) return l_true; - } if (m_model.get()) { SASSERT(validate_assumptions(*m_model.get(), asms)); - if (!project_qe(asms)) return l_undef; + if (!project_qe(asms)) + return l_undef; } - else { + else pop(1); - } break; default: if (m_model.get()) { - if (!project(asms)) return l_undef; + if (!project(asms)) + return l_undef; } - else { + else pop(1); - } break; } break; case l_undef: + TRACE("qe", tout << "check-sat is undef\n"); return res; } } @@ -833,11 +816,10 @@ namespace qe { } } - bool get_core(expr_ref_vector& core, unsigned level) { + void get_core(expr_ref_vector& core, unsigned level) { SASSERT(validate_defs("get_core")); get_kernel(level).get_core(core); m_pred_abs.pred2lit(core); - return true; } bool minimize_core(expr_ref_vector& core, unsigned level) { @@ -905,9 +887,7 @@ namespace qe { SASSERT(m_level == 1); expr_ref fml(m); model& mdl = *m_model.get(); - if (!get_core(core, m_level)) { - return false; - } + get_core(core, m_level); SASSERT(validate_core(mdl, core)); get_vars(m_level); SASSERT(validate_assumptions(mdl, core)); @@ -927,7 +907,7 @@ namespace qe { } bool project(expr_ref_vector& core) { - if (!get_core(core, m_level)) return false; + get_core(core, m_level); TRACE("qe", display(tout); display(tout << "core\n", core);); SASSERT(m_level >= 2); expr_ref fml(m); @@ -936,6 +916,7 @@ namespace qe { SASSERT(validate_core(mdl, core)); get_vars(m_level-1); SASSERT(validate_project(mdl, core)); + mdl.set_inline(); m_mbp(force_elim(), m_avars, mdl, core); TRACE("qe", tout << "aux vars: " << m_avars << "\n";); for (app* v : m_avars) m_pred_abs.ensure_expr_level(v, m_level-1); @@ -950,14 +931,17 @@ namespace qe { if (level.max() == UINT_MAX) { num_scopes = 2*(m_level/2); } + else if (level.max() + 2 > m_level) { + // fishy - this can happen. + TRACE("qe", tout << "max-level: " << level.max() << " level: " << m_level << "\n"); + return false; + } else { - if (level.max() + 2 > m_level) return false; SASSERT(level.max() + 2 <= m_level); num_scopes = m_level - level.max(); SASSERT(num_scopes >= 2); - if ((num_scopes % 2) != 0) { + if ((num_scopes % 2) != 0) --num_scopes; - } } pop(num_scopes); diff --git a/src/sat/CMakeLists.txt b/src/sat/CMakeLists.txt index 25af69178..b16a15482 100644 --- a/src/sat/CMakeLists.txt +++ b/src/sat/CMakeLists.txt @@ -39,6 +39,7 @@ z3_add_component(sat util dd grobner + params PYG_FILES sat_asymm_branch_params.pyg sat_params.pyg diff --git a/src/sat/dimacs.cpp b/src/sat/dimacs.cpp index 629fefb7b..1d19a60c7 100644 --- a/src/sat/dimacs.cpp +++ b/src/sat/dimacs.cpp @@ -112,27 +112,6 @@ static void read_clause(Buffer & in, std::ostream& err, sat::literal_vector & li } } -template -static void read_pragma(Buffer & in, std::ostream& err, std::string& p, sat::proof_hint& h) { - skip_whitespace(in); - if (*in != 'p') - return; - ++in; - while (*in == ' ') - ++in; - while (true) { - if (*in == EOF) - break; - if (*in == '\n') { - ++in; - break; - } - p.push_back(*in); - ++in; - } - if (!p.empty()) - h.from_string(p); -} template @@ -177,25 +156,7 @@ namespace dimacs { std::ostream& operator<<(std::ostream& out, drat_pp const& p) { auto const& r = p.r; sat::status_pp pp(r.m_status, p.th); - switch (r.m_tag) { - case drat_record::tag_t::is_clause: - if (!r.m_pragma.empty()) - return out << pp << " " << r.m_lits << " 0 p " << r.m_pragma << "\n"; - return out << pp << " " << r.m_lits << " 0\n"; - case drat_record::tag_t::is_node: - return out << "e " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n"; - case drat_record::tag_t::is_sort: - return out << "s " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n"; - case drat_record::tag_t::is_decl: - return out << "f " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n"; - case drat_record::tag_t::is_bool_def: - return out << "b " << r.m_node_id << " " << r.m_args << "0\n"; - case drat_record::tag_t::is_var: - return out << "v " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n"; - case drat_record::tag_t::is_quantifier: - return out << "q " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n"; - } - return out; + return out << pp << " " << r.m_lits << " 0\n"; } char const* drat_parser::parse_identifier() { @@ -266,47 +227,10 @@ namespace dimacs { } bool drat_parser::next() { - int n, b, e, theory_id; - auto parse_ast = [&](drat_record::tag_t tag) { - ++in; - skip_whitespace(in); - n = parse_int(in, err); - skip_whitespace(in); - m_record.m_name = parse_sexpr(); - m_record.m_tag = tag; - m_record.m_node_id = n; - m_record.m_args.reset(); - while (true) { - n = parse_int(in, err); - if (n == 0) - break; - if (n < 0) - throw lex_error(); - m_record.m_args.push_back(n); - } - }; - auto parse_var = [&]() { - ++in; - skip_whitespace(in); - n = parse_int(in, err); - skip_whitespace(in); - m_record.m_name = parse_sexpr(); - m_record.m_tag = drat_record::tag_t::is_var; - m_record.m_node_id = n; - m_record.m_args.reset(); - n = parse_int(in, err); - if (n < 0) - throw lex_error(); - m_record.m_args.push_back(n); - n = parse_int(in, err); - if (n != 0) - throw lex_error(); - }; + int theory_id; try { loop: skip_whitespace(in); - m_record.m_pragma.clear(); - m_record.m_hint.reset(); switch (*in) { case EOF: return false; @@ -321,7 +245,6 @@ namespace dimacs { ++in; skip_whitespace(in); read_clause(in, err, m_record.m_lits); - m_record.m_tag = drat_record::tag_t::is_clause; m_record.m_status = sat::status::input(); break; case 'a': @@ -331,49 +254,13 @@ namespace dimacs { theory_id = read_theory_id(); skip_whitespace(in); read_clause(in, err, m_record.m_lits); - read_pragma(in, err, m_record.m_pragma, m_record.m_hint); - m_record.m_tag = drat_record::tag_t::is_clause; m_record.m_status = sat::status::th(false, theory_id); break; - case 'e': - // parse expression definition - parse_ast(drat_record::tag_t::is_node); - break; - case 'v': - parse_var(); - break; - case 'q': - parse_ast(drat_record::tag_t::is_quantifier); - break; - case 'f': - // parse function declaration - parse_ast(drat_record::tag_t::is_decl); - break; - case 's': - // parse sort declaration (not used) - parse_ast(drat_record::tag_t::is_sort); - break; - case 'b': - // parse bridge between Boolean variable identifier b - // and expression identifier e, which is of type Bool - ++in; - skip_whitespace(in); - b = parse_int(in, err); - n = parse_int(in, err); - e = parse_int(in, err); - if (e != 0) - throw lex_error(); - m_record.m_tag = drat_record::tag_t::is_bool_def; - m_record.m_node_id = b; - m_record.m_args.reset(); - m_record.m_args.push_back(n); - break; case 'd': // parse clause deletion ++in; skip_whitespace(in); read_clause(in, err, m_record.m_lits); - m_record.m_tag = drat_record::tag_t::is_clause; m_record.m_status = sat::status::deleted(); break; case 'r': @@ -383,13 +270,11 @@ namespace dimacs { skip_whitespace(in); theory_id = read_theory_id(); read_clause(in, err, m_record.m_lits); - m_record.m_tag = drat_record::tag_t::is_clause; m_record.m_status = sat::status::th(true, theory_id); break; default: // parse clause redundant modulo DRAT (or mostly just DRUP) read_clause(in, err, m_record.m_lits); - m_record.m_tag = drat_record::tag_t::is_clause; m_record.m_status = sat::status::redundant(); break; } diff --git a/src/sat/dimacs.h b/src/sat/dimacs.h index 7a5a66283..ca6aae07b 100644 --- a/src/sat/dimacs.h +++ b/src/sat/dimacs.h @@ -53,18 +53,11 @@ namespace dimacs { }; struct drat_record { - enum class tag_t { is_clause, is_node, is_decl, is_sort, is_bool_def, is_var, is_quantifier }; - tag_t m_tag{ tag_t::is_clause }; // a clause populates m_lits and m_status // a node populates m_node_id, m_name, m_args // a bool def populates m_node_id and one element in m_args sat::literal_vector m_lits; sat::status m_status = sat::status::redundant(); - unsigned m_node_id = 0; - std::string m_name; - unsigned_vector m_args; - std::string m_pragma; - sat::proof_hint m_hint; }; struct drat_pp { diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index a53a4b856..d21ec5b93 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -20,6 +20,7 @@ Revision History: #include "sat/sat_types.h" #include "sat/sat_params.hpp" #include "sat/sat_simplifier_params.hpp" +#include "params/solver_params.hpp" namespace sat { @@ -31,6 +32,8 @@ namespace sat { void config::updt_params(params_ref const & _p) { sat_params p(_p); + solver_params sp(_p); + m_max_memory = megabytes_to_bytes(p.max_memory()); symbol s = p.restart(); @@ -194,10 +197,10 @@ namespace sat { m_drat_check_unsat = p.drat_check_unsat(); m_drat_check_sat = p.drat_check_sat(); m_drat_file = p.drat_file(); - m_drat = (m_drat_check_unsat || m_drat_file.is_non_empty_string() || m_drat_check_sat) && p.threads() == 1; + m_smt_proof = p.smt_proof(); + m_drat = !p.drat_disable() && (sp.lemmas2console() || m_drat_check_unsat || m_drat_file.is_non_empty_string() || m_smt_proof.is_non_empty_string() || m_drat_check_sat) && p.threads() == 1; m_drat_binary = p.drat_binary(); m_drat_activity = p.drat_activity(); - m_drup_trim = p.drup_trim(); m_dyn_sub_res = p.dyn_sub_res(); // Parameters used in Liang, Ganesh, Poupart, Czarnecki AAAI 2016. @@ -248,13 +251,9 @@ namespace sat { m_card_solver = p.cardinality_solver(); m_xor_solver = false; // prevent users from playing with this option - sat_simplifier_params sp(_p); - m_elim_vars = sp.elim_vars(); + sat_simplifier_params ssp(_p); + m_elim_vars = ssp.elim_vars(); -#if 0 - if (m_drat && (m_xor_solver || m_card_solver)) - throw sat_param_exception("DRAT checking only works for pure CNF"); -#endif } void config::collect_param_descrs(param_descrs & r) { diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index 34ffeed5c..7d98b092c 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -177,9 +177,9 @@ namespace sat { bool m_drat; bool m_drat_binary; symbol m_drat_file; + symbol m_smt_proof; bool m_drat_check_unsat; bool m_drat_check_sat; - bool m_drup_trim; bool m_drat_activity; bool m_card_solver; diff --git a/src/sat/sat_drat.cpp b/src/sat/sat_drat.cpp index 9cac2e5ef..17de0fe3b 100644 --- a/src/sat/sat_drat.cpp +++ b/src/sat/sat_drat.cpp @@ -52,8 +52,7 @@ namespace sat { void drat::updt_config() { m_check_unsat = s.get_config().m_drat_check_unsat; m_check_sat = s.get_config().m_drat_check_sat; - m_trim = s.get_config().m_drup_trim; - m_check = m_check_unsat || m_check_sat || m_trim; + m_check = m_check_unsat || m_check_sat; m_activity = s.get_config().m_drat_activity; } @@ -130,14 +129,6 @@ namespace sat { } } buffer[len++] = '0'; - if (st.get_hint()) { - buffer[len++] = ' '; - buffer[len++] = 'p'; - buffer[len++] = ' '; - auto* ps = st.get_hint(); - for (auto ch : ps->to_string()) - buffer[len++] = ch; - } buffer[len++] = '\n'; m_out->write(buffer, len); } @@ -210,8 +201,6 @@ namespace sat { if (st.is_redundant() && st.is_sat()) verify(1, &l); - if (m_trim) - m_proof.push_back({mk_clause(1, &l, st.is_redundant()), st}); if (st.is_deleted()) return; @@ -230,8 +219,7 @@ namespace sat { IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st);); if (st.is_deleted()) { - if (m_trim) - m_proof.push_back({mk_clause(2, lits, true), st}); + ; } else { if (st.is_redundant() && st.is_sat()) @@ -255,31 +243,6 @@ namespace sat { } } - void drat::bool_def(bool_var v, unsigned n) { - if (m_out) - (*m_out) << "b " << v << " " << n << " 0\n"; - } - - void drat::def_begin(char id, unsigned n, std::string const& name) { - if (m_out) - (*m_out) << id << " " << n << " " << name; - } - - void drat::def_add_arg(unsigned arg) { - if (m_out) - (*m_out) << " " << arg; - } - - void drat::def_end() { - if (m_out) - (*m_out) << " 0\n"; - } - - void drat::log_adhoc(std::function& fn) { - if (m_out) - fn(*m_out); - } - void drat::append(clause& c, status st) { TRACE("sat_drat", pp(tout, st) << " " << c << "\n";); for (literal lit : c) declare(lit); @@ -453,6 +416,8 @@ namespace sat { void drat::verify(unsigned n, literal const* c) { if (!m_check_unsat) return; + if (m_inconsistent) + return; for (unsigned i = 0; i < n; ++i) declare(c[i]); if (is_drup(n, c)) { @@ -683,6 +648,7 @@ namespace sat { verify(0, nullptr); SASSERT(m_inconsistent); } + if (m_clause_eh) m_clause_eh->on_clause(0, nullptr, status::redundant()); } void drat::add(literal l, bool learned) { ++m_stats.m_num_add; @@ -690,6 +656,8 @@ namespace sat { if (m_out) dump(1, &l, st); if (m_bout) bdump(1, &l, st); if (m_check) append(l, st); + TRACE("sat", tout << "add " << m_clause_eh << "\n"); + if (m_clause_eh) m_clause_eh->on_clause(1, &l, st); } void drat::add(literal l1, literal l2, status st) { if (st.is_deleted()) @@ -700,6 +668,7 @@ namespace sat { if (m_out) dump(2, ls, st); if (m_bout) bdump(2, ls, st); if (m_check) append(l1, l2, st); + if (m_clause_eh) m_clause_eh->on_clause(2, ls, st); } void drat::add(clause& c, status st) { if (st.is_deleted()) @@ -709,6 +678,7 @@ namespace sat { if (m_out) dump(c.size(), c.begin(), st); if (m_bout) bdump(c.size(), c.begin(), st); if (m_check) append(mk_clause(c), st); + if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), st); } void drat::add(literal_vector const& lits, status st) { @@ -722,13 +692,16 @@ namespace sat { ++m_stats.m_num_add; if (m_check) { switch (sz) { - case 0: add(); break; + case 0: if (st.is_input()) m_inconsistent = true; else add(); break; case 1: append(lits[0], st); break; default: append(mk_clause(sz, lits, st.is_redundant()), st); break; } } if (m_out) dump(sz, lits, st); + + if (m_clause_eh) + m_clause_eh->on_clause(sz, lits, st); } void drat::add(literal_vector const& c) { @@ -748,6 +721,8 @@ namespace sat { } } } + if (m_clause_eh) + m_clause_eh->on_clause(c.size(), c.data(), status::redundant()); } void drat::del(literal l) { @@ -755,6 +730,7 @@ namespace sat { if (m_out) dump(1, &l, status::deleted()); if (m_bout) bdump(1, &l, status::deleted()); if (m_check) append(l, status::deleted()); + if (m_clause_eh) m_clause_eh->on_clause(1, &l, status::deleted()); } void drat::del(literal l1, literal l2) { @@ -763,6 +739,7 @@ namespace sat { if (m_out) dump(2, ls, status::deleted()); if (m_bout) bdump(2, ls, status::deleted()); if (m_check) append(l1, l2, status::deleted()); + if (m_clause_eh) m_clause_eh->on_clause(2, ls, status::deleted()); } void drat::del(clause& c) { @@ -780,7 +757,8 @@ namespace sat { ++m_stats.m_num_del; if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted()); - if (m_check) append(mk_clause(c), status::deleted()); + if (m_check) append(mk_clause(c), status::deleted()); + if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted()); } clause& drat::mk_clause(clause& c) { @@ -796,23 +774,9 @@ namespace sat { if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted()); if (m_check) append(mk_clause(c.size(), c.begin(), true), status::deleted()); + if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted()); } - // - // placeholder for trim function. - // 1. trail contains justification for the empty clause. - // 2. backward pass to prune. - // - svector> drat::trim() { - SASSERT(m_units.empty()); - svector> proof; - for (auto const& [c, st] : m_proof) - if (!st.is_deleted()) - proof.push_back({c,st}); - return proof; - } - - void drat::check_model(model const& m) { } @@ -844,196 +808,4 @@ namespace sat { return out; } - - std::string proof_hint::to_string() const { - std::ostringstream ous; - switch (m_ty) { - case hint_type::null_h: - return std::string(); - case hint_type::farkas_h: - ous << "farkas "; - break; - case hint_type::bound_h: - ous << "bound "; - break; - case hint_type::implied_eq_h: - ous << "implied_eq "; - break; - default: - UNREACHABLE(); - break; - } - for (auto const& [q, l] : m_literals) - ous << rational(q) << " * " << l << " "; - for (auto const& [a, b] : m_eqs) - ous << " = " << a << " " << b << " "; - for (auto const& [a, b] : m_diseqs) - ous << " != " << a << " " << b << " "; - return ous.str(); - } - - void proof_hint::from_string(char const* s) { - proof_hint& h = *this; - h.reset(); - h.m_ty = hint_type::null_h; - if (!s) - return; - auto ws = [&]() { - while (*s == ' ' || *s == '\n' || *s == '\t') - ++s; - }; - - auto parse_type = [&]() { - if (0 == strncmp(s, "farkas", 6)) { - h.m_ty = hint_type::farkas_h; - s += 6; - return true; - } - if (0 == strncmp(s, "bound", 5)) { - h.m_ty = hint_type::bound_h; - s += 5; - return true; - } - if (0 == strncmp(s, "implied_eq", 10)) { - h.m_ty = hint_type::implied_eq_h; - s += 10; - return true; - } - return false; - }; - - sbuffer buff; - auto parse_coeff = [&]() { - buff.reset(); - while (*s && *s != ' ') { - buff.push_back(*s); - ++s; - } - buff.push_back(0); - return rational(buff.data()); - }; - - auto parse_literal = [&]() { - rational r = parse_coeff(); - if (!r.is_int()) - return sat::null_literal; - if (r < 0) - return sat::literal((-r).get_unsigned(), true); - return sat::literal(r.get_unsigned(), false); - }; - auto parse_coeff_literal = [&]() { - if (*s == '=') { - ++s; - ws(); - unsigned a = parse_coeff().get_unsigned(); - ws(); - unsigned b = parse_coeff().get_unsigned(); - h.m_eqs.push_back(std::make_pair(a, b)); - return true; - } - if (*s == '!' && *(s + 1) == '=') { - s += 2; - ws(); - unsigned a = parse_coeff().get_unsigned(); - ws(); - unsigned b = parse_coeff().get_unsigned(); - h.m_diseqs.push_back(std::make_pair(a, b)); - return true; - } - rational coeff = parse_coeff(); - ws(); - if (*s == '*') { - ++s; - ws(); - sat::literal lit = parse_literal(); - h.m_literals.push_back(std::make_pair(coeff, lit)); - return true; - } - return false; - }; - - ws(); - if (!parse_type()) - return; - ws(); - while (*s) { - if (!parse_coeff_literal()) - return; - ws(); - } - } - -#if 0 - // debugging code - bool drat::is_clause(clause& c, literal l1, literal l2, literal l3, drat::status st1, drat::status st2) { - //if (st1 != st2) return false; - if (c.size() != 3) return false; - if (l1 == c[0]) { - if (l2 == c[1] && l3 == c[2]) return true; - if (l2 == c[2] && l3 == c[1]) return true; - } - if (l2 == c[0]) { - if (l1 == c[1] && l3 == c[2]) return true; - if (l1 == c[2] && l3 == c[1]) return true; - } - if (l3 == c[0]) { - if (l1 == c[1] && l2 == c[2]) return true; - if (l1 == c[2] && l2 == c[1]) return true; - } - return false; - } -#endif - - -#if 0 - if (!m_inconsistent) { - literal_vector lits(n, c); - IF_VERBOSE(0, verbose_stream() << "not drup " << lits << "\n"); - for (unsigned v = 0; v < m_assignment.size(); ++v) { - lbool val = m_assignment[v]; - if (val != l_undef) { - IF_VERBOSE(0, verbose_stream() << literal(v, false) << " |-> " << val << "\n"); - } - } - for (clause* cp : s.m_clauses) { - clause& cl = *cp; - bool found = false; - for (literal l : cl) { - if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) { - found = true; - break; - } - } - if (!found) { - IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n"); - } - } - for (clause* cp : s.m_learned) { - clause& cl = *cp; - bool found = false; - for (literal l : cl) { - if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) { - found = true; - break; - } - } - if (!found) { - IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n"); - } - } - svector bin; - s.collect_bin_clauses(bin, true); - for (auto& b : bin) { - bool found = false; - if (m_assignment[b.first.var()] != (b.first.sign() ? l_true : l_false)) found = true; - if (m_assignment[b.second.var()] != (b.second.sign() ? l_true : l_false)) found = true; - if (!found) { - IF_VERBOSE(0, verbose_stream() << "Bin clause is false under assignment: " << b.first << " " << b.second << "\n"); - } - } - IF_VERBOSE(0, s.display(verbose_stream())); - exit(0); - } -#endif - } diff --git a/src/sat/sat_drat.h b/src/sat/sat_drat.h index 18ca7b7b8..452b69701 100644 --- a/src/sat/sat_drat.h +++ b/src/sat/sat_drat.h @@ -60,6 +60,11 @@ namespace sat { class justification; class clause; + struct clause_eh { + virtual ~clause_eh() {} + virtual void on_clause(unsigned, literal const*, status) = 0; + }; + class drat { struct stats { unsigned m_num_drup = 0; @@ -73,6 +78,7 @@ namespace sat { watched_clause(clause* c, literal l1, literal l2): m_clause(c), m_l1(l1), m_l2(l2) {} }; + clause_eh* m_clause_eh = nullptr; svector m_watched_clauses; typedef svector watch; solver& s; @@ -89,9 +95,9 @@ namespace sat { bool m_check_sat = false; bool m_check = false; bool m_activity = false; - bool m_trim = false; stats m_stats; + void dump_activity(); void dump(unsigned n, literal const* c, status st); void bdump(unsigned n, literal const* c, status st); @@ -138,17 +144,9 @@ namespace sat { void add(literal_vector const& c); // add learned clause void add(unsigned sz, literal const* lits, status st); - // support for SMT - connect Boolean variables with AST nodes - // associate AST node id with Boolean variable v - void bool_def(bool_var v, unsigned n); + void set_clause_eh(clause_eh& clause_eh) { m_clause_eh = &clause_eh; } - // declare AST node n with 'name' and arguments arg - void def_begin(char id, unsigned n, std::string const& name); - void def_add_arg(unsigned arg); - void def_end(); - - // ad-hoc logging until a format is developed - void log_adhoc(std::function& fn); + std::ostream* out() { return m_out; } bool is_cleaned(clause& c) const; void del(literal l); @@ -175,8 +173,6 @@ namespace sat { svector> const& units() { return m_units; } bool is_drup(unsigned n, literal const* c, literal_vector& units); solver& get_solver() { return s; } - - svector> trim(); }; diff --git a/src/sat/sat_extension.h b/src/sat/sat_extension.h index ac080ba62..e906c93de 100644 --- a/src/sat/sat_extension.h +++ b/src/sat/sat_extension.h @@ -70,7 +70,7 @@ namespace sat { solver* m_solver { nullptr }; public: extension(symbol const& name, int id): m_id(id), m_name(name) { } - virtual ~extension() {} + virtual ~extension() = default; int get_id() const { return m_id; } void set_solver(solver* s) { m_solver = s; } solver& s() { return *m_solver; } diff --git a/src/sat/sat_lookahead.h b/src/sat/sat_lookahead.h index a50d3ed37..784293461 100644 --- a/src/sat/sat_lookahead.h +++ b/src/sat/sat_lookahead.h @@ -353,7 +353,7 @@ namespace sat { candidate(bool_var v, double r): m_var(v), m_rating(r) {} }; svector m_candidates; - uint_set m_select_lookahead_vars; + tracked_uint_set m_select_lookahead_vars; double get_rating(bool_var v) const { return m_rating[v]; } double get_rating(literal l) const { return get_rating(l.var()); } diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 71ac81715..41bfa7afa 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -46,11 +46,12 @@ def_module_params('sat', ('backtrack.conflicts', UINT, 4000, 'number of conflicts before enabling chronological backtracking'), ('threads', UINT, 1, 'number of parallel threads to use'), ('dimacs.core', BOOL, False, 'extract core from DIMACS benchmarks'), + ('drat.disable', BOOL, False, 'override anything that enables DRAT'), + ('smt.proof', SYMBOL, '', 'add SMT proof to file'), ('drat.file', SYMBOL, '', 'file to dump DRAT proofs'), ('drat.binary', BOOL, False, 'use Binary DRAT output format'), ('drat.check_unsat', BOOL, False, 'build up internal proof and check'), ('drat.check_sat', BOOL, False, 'build up internal trace, check satisfying model'), - ('drup.trim', BOOL, False, 'build and trim drup proof'), ('drat.activity', BOOL, False, 'dump variable activities'), ('cardinality.solver', BOOL, True, 'use cardinality solver'), ('pb.solver', SYMBOL, 'solver', 'method for handling Pseudo-Boolean constraints: circuit (arithmetical circuit), sorting (sorting circuit), totalizer (use totalizer encoding), binary_merge, segmented, solver (use native solver)'), diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 9ffe5e297..c6722bd93 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -403,6 +403,7 @@ namespace sat { extension::scoped_drating _sd(*m_ext.get()); if (j.get_kind() == justification::EXT_JUSTIFICATION) fill_ext_antecedents(lit, j, false); + TRACE("sat", tout << "drat-unit\n"); m_drat.add(lit, m_searching); } @@ -413,6 +414,7 @@ namespace sat { clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) { bool redundant = st.is_redundant(); TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";); + bool logged = false; if (!redundant || !st.is_sat()) { unsigned old_sz = num_lits; bool keep = simplify_clause(num_lits, lits); @@ -421,13 +423,14 @@ namespace sat { return nullptr; // clause is equivalent to true. } // if an input clause is simplified, then log the simplified version as learned - if (m_config.m_drat && old_sz > num_lits) + if (m_config.m_drat && old_sz > num_lits) { drat_log_clause(num_lits, lits, st); + logged = true; + } ++m_stats.m_non_learned_generation; - if (!m_searching) { - m_mc.add_clause(num_lits, lits); - } + if (!m_searching) + m_mc.add_clause(num_lits, lits); } @@ -436,7 +439,7 @@ namespace sat { set_conflict(); return nullptr; case 1: - if (m_config.m_drat && (!st.is_sat() || st.is_input())) + if (!logged && m_config.m_drat && (!st.is_sat() || st.is_input())) drat_log_clause(num_lits, lits, st); assign_unit(lits[0]); return nullptr; @@ -938,16 +941,17 @@ namespace sat { m_inconsistent = true; m_conflict = c; m_not_l = not_l; + TRACE("sat", display(display_justification(tout << "conflict " << not_l << " ", c) << "\n")); } void solver::assign_core(literal l, justification j) { SASSERT(value(l) == l_undef); + SASSERT(!m_trail.contains(l) && !m_trail.contains(~l)); TRACE("sat_assign_core", tout << l << " " << j << "\n";); if (j.level() == 0) { if (m_config.m_drat) drat_log_unit(l, j); - if (!m_config.m_drup_trim) - j = justification(0); // erase justification for level 0 + j = justification(0); // erase justification for level 0 } else { VERIFY(!at_base_lvl()); @@ -1801,24 +1805,21 @@ namespace sat { void solver::init_assumptions(unsigned num_lits, literal const* lits) { - if (num_lits == 0 && m_user_scope_literals.empty()) { - return; - } + if (num_lits == 0 && m_user_scope_literals.empty()) + return; SASSERT(at_base_lvl()); reset_assumptions(); push(); propagate(false); - if (inconsistent()) { - return; - } + if (inconsistent()) + return; TRACE("sat", tout << literal_vector(num_lits, lits) << "\n"; - if (!m_user_scope_literals.empty()) { - tout << "user literals: " << m_user_scope_literals << "\n"; - } + if (!m_user_scope_literals.empty()) + tout << "user literals: " << m_user_scope_literals << "\n"; m_mc.display(tout); ); @@ -1895,13 +1896,11 @@ namespace sat { tout << "consistent: " << !inconsistent() << "\n"; for (literal a : m_assumptions) { index_set s; - if (m_antecedents.find(a.var(), s)) { - tout << a << ": "; display_index_set(tout, s) << "\n"; - } - } - for (literal lit : m_user_scope_literals) { - tout << "user " << lit << "\n"; + if (m_antecedents.find(a.var(), s)) + tout << a << ": "; display_index_set(tout, s) << "\n"; } + for (literal lit : m_user_scope_literals) + tout << "user " << lit << "\n"; ); } } @@ -2417,7 +2416,9 @@ namespace sat { m_conflict_lvl = get_max_lvl(m_not_l, m_conflict, unique_max); justification js = m_conflict; - if (m_conflict_lvl <= 1 && tracking_assumptions()) { + if (m_conflict_lvl <= 1 && (!m_assumptions.empty() || + !m_ext_assumption_set.empty() || + !m_user_scope_literals.empty())) { TRACE("sat", tout << "unsat core\n";); resolve_conflict_for_unsat_core(); return l_false; @@ -3652,11 +3653,14 @@ namespace sat { } } m_trail.shrink(old_sz); + DEBUG_CODE(for (literal l : m_trail) SASSERT(lvl(l.var()) <= new_lvl);); m_qhead = m_trail.size(); if (!m_replay_assign.empty()) IF_VERBOSE(20, verbose_stream() << "replay assign: " << m_replay_assign.size() << "\n"); CTRACE("sat", !m_replay_assign.empty(), tout << "replay-assign: " << m_replay_assign << "\n";); for (unsigned i = m_replay_assign.size(); i-- > 0; ) { literal lit = m_replay_assign[i]; + SASSERT(value(lit) == l_true); + SASSERT(!m_trail.contains(lit) && !m_trail.contains(~lit)); m_trail.push_back(lit); } @@ -3707,11 +3711,11 @@ namespace sat { // void solver::user_push() { - pop_to_base_level(); m_free_var_freeze.push_back(m_free_vars); m_free_vars.reset(); // resetting free_vars forces new variables to be assigned above new_v bool_var new_v = mk_var(true, false); + SASSERT(new_v + 1 == m_justification.size()); // there are no active variables that have higher values literal lit = literal(new_v, false); m_user_scope_literals.push_back(lit); m_cut_simplifier = nullptr; // for simplicity, wipe it out @@ -3722,13 +3726,13 @@ namespace sat { void solver::user_pop(unsigned num_scopes) { unsigned old_sz = m_user_scope_literals.size() - num_scopes; - bool_var max_var = m_user_scope_literals[old_sz].var(); + bool_var max_var = m_user_scope_literals[old_sz].var(); m_user_scope_literals.shrink(old_sz); pop_to_base_level(); if (m_ext) m_ext->user_pop(num_scopes); - + gc_vars(max_var); TRACE("sat", display(tout);); @@ -3741,7 +3745,7 @@ namespace sat { m_free_vars.append(m_free_var_freeze[old_sz]); m_free_var_freeze.shrink(old_sz); scoped_suspend_rlimit _sp(m_rlimit); - propagate(false); + propagate(false); } void solver::pop_to_base_level() { @@ -4832,20 +4836,22 @@ namespace sat { return true; } + void solver::init_ts(unsigned n, svector& v, unsigned& ts) { + if (v.empty()) + ts = 0; + + ts++; + if (ts == 0) { + ts = 1; + v.reset(); + } + while (v.size() < n) + v.push_back(0); + } + void solver::init_visited() { - if (m_visited.empty()) { - m_visited_ts = 0; - } - m_visited_ts++; - if (m_visited_ts == 0) { - m_visited_ts = 1; - m_visited.reset(); - } - while (m_visited.size() < 2*num_vars()) { - m_visited.push_back(0); - } + init_ts(2 * num_vars(), m_visited, m_visited_ts); } - }; diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h index e5b13af98..0b01b777c 100644 --- a/src/sat/sat_solver.h +++ b/src/sat/sat_solver.h @@ -84,7 +84,7 @@ namespace sat { }; struct no_drat_params : public params_ref { - no_drat_params() { set_sym("drat.file", symbol()); } + no_drat_params() { set_bool("drat.disable", true); } }; class solver : public solver_core { @@ -343,6 +343,7 @@ namespace sat { void push_reinit_stack(clause & c); void push_reinit_stack(literal l1, literal l2); + void init_ts(unsigned n, svector& v, unsigned& ts); void init_visited(); void mark_visited(literal l) { m_visited[l.index()] = m_visited_ts; } void mark_visited(bool_var v) { mark_visited(literal(v, false)); } @@ -527,20 +528,21 @@ namespace sat { unsigned m_conflicts_since_init { 0 }; unsigned m_restarts { 0 }; - unsigned m_restart_next_out { 0 }; - unsigned m_conflicts_since_restart { 0 }; - bool m_force_conflict_analysis { false }; - unsigned m_simplifications { 0 }; - unsigned m_restart_threshold { 0 }; - unsigned m_luby_idx { 0 }; - unsigned m_conflicts_since_gc { 0 }; - unsigned m_gc_threshold { 0 }; - unsigned m_defrag_threshold { 0 }; - unsigned m_num_checkpoints { 0 }; - double m_min_d_tk { 0 } ; - unsigned m_next_simplify { 0 }; - bool m_simplify_enabled { true }; - bool m_restart_enabled { true }; + unsigned m_restart_next_out = 0; + unsigned m_conflicts_since_restart = 0; + bool m_force_conflict_analysis = false; + unsigned m_simplifications = 0; + unsigned m_restart_threshold = 0; + unsigned m_luby_idx = 0; + unsigned m_conflicts_since_gc = 0; + unsigned m_gc_threshold = 0; + unsigned m_defrag_threshold = 0; + unsigned m_num_checkpoints = 0; + double m_min_d_tk = 0.0 ; + unsigned m_next_simplify = 0; + double m_simplify_mult = 1.5; + bool m_simplify_enabled = true; + bool m_restart_enabled = true; bool guess(bool_var next); bool decide(); bool_var next_var(); @@ -713,7 +715,6 @@ namespace sat { // // ----------------------- public: - void set_should_simplify() { m_next_simplify = m_conflicts_since_init; } bool_var_vector const& get_vars_to_reinit() const { return m_vars_to_reinit; } bool is_probing() const { return m_is_probing; } bool is_free(bool_var v) const { return m_free_vars.contains(v); } diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index a83cb6a45..981d91072 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -596,10 +596,10 @@ public: void convert_internalized() { m_solver.pop_to_base_level(); - if (!is_internalized() && m_fmls_head > 0) { - internalize_formulas(); - } - if (!is_internalized() || m_internalized_converted) return; + if (!is_internalized() && m_fmls_head > 0) + internalize_formulas(); + if (!is_internalized() || m_internalized_converted) + return; sat2goal s2g; m_cached_mc = nullptr; goal g(m, false, true, false); @@ -657,7 +657,8 @@ public: } euf::solver* ensure_euf() { - auto* ext = dynamic_cast(m_solver.get_extension()); + m_goal2sat.init(m, m_params, m_solver, m_map, m_dep2asm, is_incremental()); + auto* ext = m_goal2sat.ensure_euf(); return ext; } diff --git a/src/sat/sat_solver_core.h b/src/sat/sat_solver_core.h index 407deae5c..5c8b7e315 100644 --- a/src/sat/sat_solver_core.h +++ b/src/sat/sat_solver_core.h @@ -31,7 +31,7 @@ namespace sat { reslimit& m_rlimit; public: solver_core(reslimit& l) : m_rlimit(l) {} - virtual ~solver_core() {} + virtual ~solver_core() = default; // add clauses virtual void add_clause(unsigned n, literal* lits, status st) = 0; diff --git a/src/sat/sat_types.h b/src/sat/sat_types.h index fa42f0712..4e119a2ae 100644 --- a/src/sat/sat_types.h +++ b/src/sat/sat_types.h @@ -80,7 +80,7 @@ namespace sat { class i_local_search { public: - virtual ~i_local_search() {} + virtual ~i_local_search() = default; virtual void add(solver const& s) = 0; virtual void updt_params(params_ref const& p) = 0; virtual void set_seed(unsigned s) = 0; @@ -94,22 +94,9 @@ namespace sat { }; - enum class hint_type { - null_h, - farkas_h, - bound_h, - implied_eq_h, - }; - - struct proof_hint { - hint_type m_ty = hint_type::null_h; - vector> m_literals; - vector> m_eqs; - vector> m_diseqs; - void reset() { m_ty = hint_type::null_h; m_literals.reset(); m_eqs.reset(); m_diseqs.reset(); } - std::string to_string() const; - void from_string(char const* s); - void from_string(std::string const& s) { from_string(s.c_str()); } + class proof_hint { + public: + virtual ~proof_hint() {} }; class status { diff --git a/src/sat/smt/CMakeLists.txt b/src/sat/smt/CMakeLists.txt index aefe8cac9..7c8fb55c1 100644 --- a/src/sat/smt/CMakeLists.txt +++ b/src/sat/smt/CMakeLists.txt @@ -22,6 +22,7 @@ z3_add_component(sat_smt euf_invariant.cpp euf_model.cpp euf_proof.cpp + euf_proof_checker.cpp euf_relevancy.cpp euf_solver.cpp fpa_solver.cpp diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index 517efcfe3..4d1afb4cc 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -264,11 +264,12 @@ namespace arith { SASSERT(k1 != k2 || kind1 != kind2); auto bin_clause = [&](sat::literal l1, sat::literal l2) { - sat::proof_hint* bound_params = nullptr; + arith_proof_hint* bound_params = nullptr; if (ctx.use_drat()) { - bound_params = &m_farkas2; - m_farkas2.m_literals[0] = std::make_pair(rational(1), ~l1); - m_farkas2.m_literals[1] = std::make_pair(rational(1), ~l2); + m_arith_hint.set_type(ctx, hint_type::farkas_h); + m_arith_hint.add_lit(rational(1), ~l1); + m_arith_hint.add_lit(rational(1), ~l2); + bound_params = m_arith_hint.mk(ctx); } add_clause(l1, l2, bound_params); }; diff --git a/src/sat/smt/arith_diagnostics.cpp b/src/sat/smt/arith_diagnostics.cpp index 4f016746c..3c3235df1 100644 --- a/src/sat/smt/arith_diagnostics.cpp +++ b/src/sat/smt/arith_diagnostics.cpp @@ -15,6 +15,8 @@ Author: --*/ +#include "ast/ast_util.h" +#include "ast/scoped_proof.h" #include "sat/smt/euf_solver.h" #include "sat/smt/arith_solver.h" @@ -81,7 +83,6 @@ namespace arith { } void solver::explain_assumptions() { - m_arith_hint.reset(); unsigned i = 0; for (auto const & ev : m_explanation) { ++i; @@ -91,14 +92,12 @@ namespace arith { switch (m_constraint_sources[idx]) { case inequality_source: { literal lit = m_inequalities[idx]; - m_arith_hint.m_literals.push_back({ev.coeff(), lit}); + m_arith_hint.add_lit(ev.coeff(), lit); break; } case equality_source: { auto [u, v] = m_equalities[idx]; - ctx.drat_log_expr(u->get_expr()); - ctx.drat_log_expr(v->get_expr()); - m_arith_hint.m_eqs.push_back({u->get_expr_id(), v->get_expr_id()}); + m_arith_hint.add_eq(u, v); break; } default: @@ -115,22 +114,65 @@ namespace arith { * such that there is a r >= 1 * (r1*a1+..+r_k*a_k) = r*a, (r1*b1+..+r_k*b_k) <= r*b */ - sat::proof_hint const* solver::explain(sat::hint_type ty, sat::literal lit) { + arith_proof_hint const* solver::explain(hint_type ty, sat::literal lit) { if (!ctx.use_drat()) return nullptr; - m_arith_hint.m_ty = ty; + m_arith_hint.set_type(ctx, ty); explain_assumptions(); if (lit != sat::null_literal) - m_arith_hint.m_literals.push_back({rational(1), ~lit}); - return &m_arith_hint; + m_arith_hint.add_lit(rational(1), ~lit); + return m_arith_hint.mk(ctx); } - sat::proof_hint const* solver::explain_implied_eq(euf::enode* a, euf::enode* b) { + arith_proof_hint const* solver::explain_implied_eq(euf::enode* a, euf::enode* b) { if (!ctx.use_drat()) return nullptr; - m_arith_hint.m_ty = sat::hint_type::implied_eq_h; + m_arith_hint.set_type(ctx, hint_type::implied_eq_h); explain_assumptions(); - m_arith_hint.m_diseqs.push_back({a->get_expr_id(), b->get_expr_id()}); - return &m_arith_hint; + m_arith_hint.add_diseq(a, b); + return m_arith_hint.mk(ctx); + } + + expr* arith_proof_hint::get_hint(euf::solver& s) const { + ast_manager& m = s.get_manager(); + family_id fid = m.get_family_id("arith"); + arith_util arith(m); + solver& a = dynamic_cast(*s.fid2solver(fid)); + char const* name; + switch (m_ty) { + case hint_type::farkas_h: + name = "farkas"; + break; + case hint_type::bound_h: + name = "bound"; + break; + case hint_type::implied_eq_h: + name = "implied-eq"; + break; + } + rational lc(1); + for (unsigned i = m_lit_head; i < m_lit_tail; ++i) + lc = lcm(lc, denominator(a.m_arith_hint.lit(i).first)); + + expr_ref_vector args(m); + sort_ref_vector sorts(m); + for (unsigned i = m_lit_head; i < m_lit_tail; ++i) { + auto const& [coeff, lit] = a.m_arith_hint.lit(i); + args.push_back(arith.mk_int(abs(coeff*lc))); + args.push_back(s.literal2expr(lit)); + } + for (unsigned i = m_eq_head; i < m_eq_tail; ++i) { + auto const& [x, y, is_eq] = a.m_arith_hint.eq(i); + expr_ref eq(m.mk_eq(x->get_expr(), y->get_expr()), m); + if (!is_eq) eq = m.mk_not(eq); + args.push_back(arith.mk_int(1)); + args.push_back(eq); + } + for (expr* arg : args) + sorts.push_back(arg->get_sort()); + sort* range = m.mk_proof_sort(); + func_decl* d = m.mk_func_decl(symbol(name), args.size(), sorts.data(), range); + expr* r = m.mk_app(d, args); + return r; } } diff --git a/src/sat/smt/arith_proof_checker.h b/src/sat/smt/arith_proof_checker.h index c37a4f7c2..1a8a8df27 100644 --- a/src/sat/smt/arith_proof_checker.h +++ b/src/sat/smt/arith_proof_checker.h @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2020 Microsoft Corporation +Copyright (c) 2022 Microsoft Corporation Module Name: @@ -11,7 +11,15 @@ Abstract: Author: - Nikolaj Bjorner (nbjorner) 2020-09-08 + Nikolaj Bjorner (nbjorner) 2022-08-28 + +Notes: + +The module assumes a limited repertoire of arithmetic proof rules. + +- farkas - inequalities, equalities and disequalities with coefficients +- implied-eq - last literal is a disequality. The literals before imply the corresponding equality. +- bound - last literal is a bound. It is implied by prior literals. --*/ #pragma once @@ -19,11 +27,12 @@ Author: #include "util/obj_pair_set.h" #include "ast/ast_trail.h" #include "ast/arith_decl_plugin.h" +#include "sat/smt/euf_proof_checker.h" namespace arith { - class proof_checker { + class proof_checker : public euf::proof_checker_plugin { struct row { obj_map m_coeffs; rational m_coeff; @@ -32,16 +41,19 @@ namespace arith { m_coeff = 0; } }; - + ast_manager& m; - arith_util a; + arith_util a; vector> m_todo; - bool m_strict = false; - row m_ineq; - row m_conseq; - vector m_eqs; - vector m_ineqs; - vector m_diseqs; + bool m_strict = false; + row m_ineq; + row m_conseq; + vector m_eqs; + vector m_ineqs; + vector m_diseqs; + symbol m_farkas; + symbol m_implied_eq; + symbol m_bound; void add(row& r, expr* v, rational const& coeff) { rational coeff1; @@ -131,11 +143,15 @@ namespace arith { SASSERT(m_todo.empty()); m_todo.push_back({ mul, e }); rational coeff1; - expr* e1, *e2; + expr* e1, *e2, *e3; for (unsigned i = 0; i < m_todo.size(); ++i) { auto [coeff, e] = m_todo[i]; if (a.is_mul(e, e1, e2) && a.is_numeral(e1, coeff1)) m_todo.push_back({coeff*coeff1, e2}); + else if (a.is_mul(e, e1, e2) && a.is_uminus(e1, e3) && a.is_numeral(e3, coeff1)) + m_todo.push_back({-coeff*coeff1, e2}); + else if (a.is_mul(e, e1, e2) && a.is_uminus(e2, e3) && a.is_numeral(e3, coeff1)) + m_todo.push_back({ -coeff * coeff1, e1 }); else if (a.is_mul(e, e1, e2) && a.is_numeral(e2, coeff1)) m_todo.push_back({coeff*coeff1, e1}); else if (a.is_add(e)) @@ -149,6 +165,8 @@ namespace arith { } else if (a.is_numeral(e, coeff1)) r.m_coeff += coeff*coeff1; + else if (a.is_uminus(e, e1) && a.is_numeral(e1, coeff1)) + r.m_coeff -= coeff*coeff1; else add(r, e, coeff); } @@ -296,10 +314,16 @@ namespace arith { rows.push_back(row()); return rows.back(); } - public: - proof_checker(ast_manager& m): m(m), a(m) {} + proof_checker(ast_manager& m): + m(m), + a(m), + m_farkas("farkas"), + m_implied_eq("implied-eq"), + m_bound("bound") {} + + ~proof_checker() override {} void reset() { m_ineq.reset(); @@ -313,7 +337,8 @@ namespace arith { bool add_ineq(rational const& coeff, expr* e, bool sign) { if (!m_diseqs.empty()) return add_literal(fresh(m_ineqs), abs(coeff), e, sign); - return add_literal(m_ineq, abs(coeff), e, sign); + else + return add_literal(m_ineq, abs(coeff), e, sign); } bool add_conseq(rational const& coeff, expr* e, bool sign) { @@ -350,6 +375,76 @@ namespace arith { return out; } + bool check(expr_ref_vector const& clause, app* jst, expr_ref_vector& units) override { + reset(); + expr_mark pos, neg; + for (expr* e : clause) + if (m.is_not(e, e)) + neg.mark(e, true); + else + pos.mark(e, true); + + if (jst->get_name() != m_farkas && + jst->get_name() != m_bound && + jst->get_name() != m_implied_eq) { + IF_VERBOSE(0, verbose_stream() << "unhandled inference " << mk_pp(jst, m) << "\n"); + return false; + } + bool is_bound = jst->get_name() == m_bound; + bool even = true; + rational coeff; + expr* x, * y; + unsigned j = 0; + for (expr* arg : *jst) { + if (even) { + if (!a.is_numeral(arg, coeff)) { + IF_VERBOSE(0, verbose_stream() << "not numeral " << mk_pp(jst, m) << "\n"); + return false; + } + } + else { + bool sign = m.is_not(arg, arg); + if (a.is_le(arg) || a.is_lt(arg) || a.is_ge(arg) || a.is_gt(arg)) { + if (is_bound && j + 1 == jst->get_num_args()) + add_conseq(coeff, arg, sign); + else + add_ineq(coeff, arg, sign); + } + else if (m.is_eq(arg, x, y)) { + if (sign) + add_diseq(x, y); + else + add_eq(x, y); + } + else { + IF_VERBOSE(0, verbose_stream() << "not a recognized arithmetical relation " << mk_pp(arg, m) << "\n"); + return false; + } + + if (sign && !pos.is_marked(arg)) { + units.push_back(m.mk_not(arg)); + pos.mark(arg, false); + } + else if (!sign && !neg.is_marked(arg)) { + units.push_back(arg); + neg.mark(arg, false); + } + } + even = !even; + ++j; + } + if (check()) + return true; + + IF_VERBOSE(0, verbose_stream() << "did not check condition\n" << mk_pp(jst, m) << "\n"; display(verbose_stream()); ); + return false; + } + + void register_plugins(euf::proof_checker& pc) override { + pc.register_plugin(m_farkas, this); + pc.register_plugin(m_bound, this); + pc.register_plugin(m_implied_eq, this); + } }; diff --git a/src/sat/smt/arith_solver.cpp b/src/sat/smt/arith_solver.cpp index fc3677cb9..c5730c19c 100644 --- a/src/sat/smt/arith_solver.cpp +++ b/src/sat/smt/arith_solver.cpp @@ -39,8 +39,6 @@ namespace arith { lp().settings().set_random_seed(get_config().m_random_seed); m_lia = alloc(lp::int_solver, *m_solver.get()); - m_farkas2.m_ty = sat::hint_type::farkas_h; - m_farkas2.m_literals.resize(2); } solver::~solver() { @@ -197,11 +195,12 @@ namespace arith { reset_evidence(); m_core.push_back(lit1); TRACE("arith", tout << lit2 << " <- " << m_core << "\n";); - sat::proof_hint* ph = nullptr; + arith_proof_hint* ph = nullptr; if (ctx.use_drat()) { - ph = &m_farkas2; - m_farkas2.m_literals[0] = std::make_pair(rational(1), lit1); - m_farkas2.m_literals[1] = std::make_pair(rational(1), ~lit2); + m_arith_hint.set_type(ctx, hint_type::farkas_h); + m_arith_hint.add_lit(rational(1), lit1); + m_arith_hint.add_lit(rational(1), ~lit2); + ph = m_arith_hint.mk(ctx); } assign(lit2, m_core, m_eqs, ph); ++m_stats.m_bounds_propagations; @@ -262,7 +261,7 @@ namespace arith { TRACE("arith", for (auto lit : m_core) tout << lit << ": " << s().value(lit) << "\n";); DEBUG_CODE(for (auto lit : m_core) { VERIFY(s().value(lit) == l_true); }); ++m_stats.m_bound_propagations1; - assign(lit, m_core, m_eqs, explain(sat::hint_type::bound_h, lit)); + assign(lit, m_core, m_eqs, explain(hint_type::bound_h, lit)); } if (should_refine_bounds() && first) @@ -378,7 +377,7 @@ namespace arith { reset_evidence(); m_explanation.clear(); lp().explain_implied_bound(be, m_bp); - assign(bound, m_core, m_eqs, explain(sat::hint_type::farkas_h, bound)); + assign(bound, m_core, m_eqs, explain(hint_type::farkas_h, bound)); } @@ -386,20 +385,16 @@ namespace arith { TRACE("arith", tout << b << "\n";); lp::constraint_index ci = b.get_constraint(is_true); lp().activate(ci); - if (is_infeasible()) { + if (is_infeasible()) return; - } lp::lconstraint_kind k = bound2constraint_kind(b.is_int(), b.get_bound_kind(), is_true); - if (k == lp::LT || k == lp::LE) { + if (k == lp::LT || k == lp::LE) ++m_stats.m_assert_lower; - } - else { + else ++m_stats.m_assert_upper; - } inf_rational value = b.get_value(is_true); - if (propagate_eqs() && value.is_rational()) { + if (propagate_eqs() && value.is_rational()) propagate_eqs(b.tv(), ci, k, b, value.get_rational()); - } #if 0 if (propagation_mode() != BP_NONE) lp().mark_rows_for_bound_prop(b.tv().id()); @@ -617,8 +612,7 @@ namespace arith { verbose_stream() << eval << " " << value << " " << ctx.bpp(n) << "\n"; verbose_stream() << n->bool_var() << " " << n->value() << " " << get_phase(n->bool_var()) << " " << ctx.bpp(n) << "\n"; verbose_stream() << *b << "\n";); - IF_VERBOSE(0, ctx.display(verbose_stream())); - IF_VERBOSE(0, verbose_stream() << mdl << "\n"); + IF_VERBOSE(0, ctx.display_validation_failure(verbose_stream(), mdl, n)); UNREACHABLE(); } } @@ -1119,16 +1113,23 @@ namespace arith { } bool solver::check_delayed_eqs() { - for (auto p : m_delayed_eqs) { + bool found_diseq = false; + if (m_delayed_eqs_qhead == m_delayed_eqs.size()) + return true; + force_push(); + ctx.push(value_trail(m_delayed_eqs_qhead)); + for (; m_delayed_eqs_qhead < m_delayed_eqs.size(); ++ m_delayed_eqs_qhead) { + auto p = m_delayed_eqs[m_delayed_eqs_qhead]; auto const& e = p.first; if (p.second) new_eq_eh(e); else if (is_eq(e.v1(), e.v2())) { mk_diseq_axiom(e); - return false; + found_diseq = true; + break; } - } - return true; + } + return !found_diseq; } lbool solver::check_lia() { @@ -1178,7 +1179,7 @@ namespace arith { app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); IF_VERBOSE(4, verbose_stream() << "cut " << b << "\n"); literal lit = expr2literal(b); - assign(lit, m_core, m_eqs, explain(sat::hint_type::bound_h, lit)); + assign(lit, m_core, m_eqs, explain(hint_type::bound_h, lit)); lia_check = l_false; break; } @@ -1200,7 +1201,7 @@ namespace arith { return lia_check; } - void solver::assign(literal lit, literal_vector const& core, svector const& eqs, sat::proof_hint const* pma) { + void solver::assign(literal lit, literal_vector const& core, svector const& eqs, euf::th_proof_hint const* pma) { if (core.size() < small_lemma_size() && eqs.empty()) { m_core2.reset(); for (auto const& c : core) @@ -1247,7 +1248,7 @@ namespace arith { for (literal& c : m_core) c.neg(); - add_clause(m_core, explain(sat::hint_type::farkas_h)); + add_clause(m_core, explain(hint_type::farkas_h)); } bool solver::is_infeasible() const { diff --git a/src/sat/smt/arith_solver.h b/src/sat/smt/arith_solver.h index 76bdeca9d..a1c89af76 100644 --- a/src/sat/smt/arith_solver.h +++ b/src/sat/smt/arith_solver.h @@ -48,8 +48,61 @@ namespace arith { typedef sat::literal_vector literal_vector; typedef lp_api::bound api_bound; + enum class hint_type { + farkas_h, + bound_h, + implied_eq_h + }; + + struct arith_proof_hint : public euf::th_proof_hint { + hint_type m_ty; + unsigned m_lit_head, m_lit_tail, m_eq_head, m_eq_tail; + arith_proof_hint(hint_type t, unsigned lh, unsigned lt, unsigned eh, unsigned et): + m_ty(t), m_lit_head(lh), m_lit_tail(lt), m_eq_head(eh), m_eq_tail(et) {} + expr* get_hint(euf::solver& s) const override; + }; + + class arith_proof_hint_builder { + vector> m_literals; + svector> m_eqs; + hint_type m_ty; + unsigned m_lit_head = 0, m_lit_tail = 0, m_eq_head = 0, m_eq_tail = 0; + void reset() { m_lit_head = m_lit_tail; m_eq_head = m_eq_tail; } + void add(euf::enode* a, euf::enode* b, bool is_eq) { + if (m_eq_tail < m_eqs.size()) + m_eqs[m_eq_tail] = std::tuple(a, b, is_eq); + else + m_eqs.push_back(std::tuple(a, b, is_eq)); + m_eq_tail++; + } + public: + void set_type(euf::solver& ctx, hint_type ty) { + ctx.push(value_trail(m_eq_tail)); + ctx.push(value_trail(m_lit_tail)); + m_ty = ty; + reset(); + } + void add_eq(euf::enode* a, euf::enode* b) { add(a, b, true); } + void add_diseq(euf::enode* a, euf::enode* b) { add(a, b, false); } + void add_lit(rational const& coeff, literal lit) { + if (m_lit_tail < m_literals.size()) + m_literals[m_lit_tail] = {coeff, lit}; + else + m_literals.push_back({coeff, lit}); + m_lit_tail++; + } + std::pair const& lit(unsigned i) const { return m_literals[i]; } + std::tuple const& eq(unsigned i) const { return m_eqs[i]; } + arith_proof_hint* mk(euf::solver& s) { + return new (s.get_region()) arith_proof_hint(m_ty, m_lit_head, m_lit_tail, m_eq_head, m_eq_tail); + } + }; + + class solver : public euf::th_euf_solver { + friend struct arith_proof_hint; + struct scope { unsigned m_bounds_lim; unsigned m_idiv_lim; @@ -165,6 +218,7 @@ namespace arith { svector m_equalities; // asserted rows corresponding to equalities. svector m_definitions; // asserted rows corresponding to definitions svector> m_delayed_eqs; + unsigned m_delayed_eqs_qhead = 0; literal_vector m_asserted; expr* m_not_handled{ nullptr }; @@ -414,15 +468,15 @@ namespace arith { void set_conflict(); void set_conflict_or_lemma(literal_vector const& core, bool is_conflict); void set_evidence(lp::constraint_index idx); - void assign(literal lit, literal_vector const& core, svector const& eqs, sat::proof_hint const* pma); + void assign(literal lit, literal_vector const& core, svector const& eqs, euf::th_proof_hint const* pma); void false_case_of_check_nla(const nla::lemma& l); void dbg_finalize_model(model& mdl); - sat::proof_hint m_arith_hint; - sat::proof_hint m_farkas2; - sat::proof_hint const* explain(sat::hint_type ty, sat::literal lit = sat::null_literal); - sat::proof_hint const* explain_implied_eq(euf::enode* a, euf::enode* b); + arith_proof_hint_builder m_arith_hint; + + arith_proof_hint const* explain(hint_type ty, sat::literal lit = sat::null_literal); + arith_proof_hint const* explain_implied_eq(euf::enode* a, euf::enode* b); void explain_assumptions(); diff --git a/src/sat/smt/array_axioms.cpp b/src/sat/smt/array_axioms.cpp index df1fecc49..2c08b3e69 100644 --- a/src/sat/smt/array_axioms.cpp +++ b/src/sat/smt/array_axioms.cpp @@ -265,7 +265,8 @@ namespace array { args1.push_back(e1); args2.push_back(e2); for (func_decl* f : funcs) { - expr* k = m.mk_app(f, e1, e2); + expr_ref k(m.mk_app(f, e1, e2), m); + rewrite(k); args1.push_back(k); args2.push_back(k); } @@ -699,6 +700,23 @@ namespace array { n->unmark1(); } + /** + * \brief check that lambda expressions are beta redexes. + * The array solver is not a decision procedure for lambdas that do not occur in beta + * redexes. + */ + bool solver::check_lambdas() { + unsigned num_vars = get_num_vars(); + for (unsigned i = 0; i < num_vars; i++) { + auto* n = var2enode(i); + if (a.is_as_array(n->get_expr()) || is_lambda(n->get_expr())) + for (euf::enode* p : euf::enode_parents(n)) + if (!ctx.is_beta_redex(p, n)) + return false; + } + return true; + } + bool solver::is_shared_arg(euf::enode* r) { SASSERT(r->is_root()); for (euf::enode* n : euf::enode_parents(r)) { diff --git a/src/sat/smt/array_internalize.cpp b/src/sat/smt/array_internalize.cpp index 68dea4d63..bd01f52da 100644 --- a/src/sat/smt/array_internalize.cpp +++ b/src/sat/smt/array_internalize.cpp @@ -252,6 +252,8 @@ namespace array { return p->get_arg(0)->get_root() == n->get_root(); if (a.is_map(p->get_expr())) return true; + if (a.is_store(p->get_expr())) + return true; return false; } diff --git a/src/sat/smt/array_solver.cpp b/src/sat/smt/array_solver.cpp index eb8986098..2481e337a 100644 --- a/src/sat/smt/array_solver.cpp +++ b/src/sat/smt/array_solver.cpp @@ -108,7 +108,9 @@ namespace array { if (m_delay_qhead < m_axiom_trail.size()) return sat::check_result::CR_CONTINUE; - + if (!check_lambdas()) + return sat::check_result::CR_GIVEUP; + // validate_check(); return sat::check_result::CR_DONE; } diff --git a/src/sat/smt/array_solver.h b/src/sat/smt/array_solver.h index f2b4fb565..fbff2afb6 100644 --- a/src/sat/smt/array_solver.h +++ b/src/sat/smt/array_solver.h @@ -218,6 +218,7 @@ namespace array { bool should_prop_upward(var_data const& d) const; bool can_beta_reduce(euf::enode* n) const { return can_beta_reduce(n->get_expr()); } bool can_beta_reduce(expr* e) const; + bool check_lambdas(); var_data& get_var_data(euf::enode* n) { return get_var_data(n->get_th_var(get_id())); } var_data& get_var_data(theory_var v) { return *m_var_data[v]; } diff --git a/src/sat/smt/bv_ackerman.cpp b/src/sat/smt/bv_ackerman.cpp index 18f0bd951..940b1ebb0 100644 --- a/src/sat/smt/bv_ackerman.cpp +++ b/src/sat/smt/bv_ackerman.cpp @@ -49,16 +49,19 @@ namespace bv { update_glue(*other); vv::push_to_front(m_queue, other); - if (other == n) { + bool do_gc = other == n; + if (other == n) new_tmp(); - gc(); - } + if (other->m_glue == 0) { + do_gc = false; remove(other); add_cc(v1, v2); } - else if (other->m_count > m_propagate_high_watermark) - s.s().set_should_simplify(); + else if (other->m_count > 2*m_propagate_high_watermark) + propagate(); + if (do_gc) + gc(); } void ackerman::used_diseq_eh(euf::theory_var v1, euf::theory_var v2) { @@ -76,8 +79,8 @@ namespace bv { new_tmp(); gc(); } - if (other->m_count > m_propagate_high_watermark) - s.s().set_should_simplify(); + if (other->m_count > 2*m_propagate_high_watermark) + propagate(); } void ackerman::update_glue(vv& v) { @@ -137,6 +140,9 @@ namespace bv { if (m_num_propagations_since_last_gc <= s.get_config().m_dack_gc) return; m_num_propagations_since_last_gc = 0; + + if (m_table.size() > m_gc_threshold) + propagate(); while (m_table.size() > m_gc_threshold) remove(m_queue->prev()); @@ -147,7 +153,6 @@ namespace bv { } void ackerman::propagate() { - SASSERT(s.s().at_base_lvl()); auto* n = m_queue; vv* k = nullptr; unsigned num_prop = static_cast(s.s().get_stats().m_conflict * s.get_config().m_dack_factor); diff --git a/src/sat/smt/bv_ackerman.h b/src/sat/smt/bv_ackerman.h index f6465abc7..aab4053a2 100644 --- a/src/sat/smt/bv_ackerman.h +++ b/src/sat/smt/bv_ackerman.h @@ -51,13 +51,13 @@ namespace bv { solver& s; table_t m_table; - vv* m_queue { nullptr }; - vv* m_tmp_vv { nullptr }; + vv* m_queue = nullptr; + vv* m_tmp_vv = nullptr; - unsigned m_gc_threshold { 100 }; - unsigned m_propagate_high_watermark { 10000 }; - unsigned m_propagate_low_watermark { 10 }; - unsigned m_num_propagations_since_last_gc { 0 }; + unsigned m_gc_threshold = 100; + unsigned m_propagate_high_watermark = 10000; + unsigned m_propagate_low_watermark = 10; + unsigned m_num_propagations_since_last_gc = 0; bool_vector m_diff_levels; void update_glue(vv& v); diff --git a/src/sat/smt/bv_delay_internalize.cpp b/src/sat/smt/bv_delay_internalize.cpp index 3b4067532..8abb0fe10 100644 --- a/src/sat/smt/bv_delay_internalize.cpp +++ b/src/sat/smt/bv_delay_internalize.cpp @@ -47,7 +47,7 @@ namespace bv { return true; unsigned num_vars = e->get_num_args(); for (expr* arg : *e) - if (!m.is_value(arg)) + if (m.is_value(arg)) --num_vars; if (num_vars <= 1) return true; @@ -72,6 +72,55 @@ namespace bv { return expr_ref(bv.mk_numeral(val, get_bv_size(v)), m); } + /** + \brief expose the multiplication circuit lazily. + It adds clauses for multiplier output one by one to enforce + the semantics of multipliers. + */ + + bool solver::check_lazy_mul(app* e, expr* arg_value, expr* mul_value) { + SASSERT(e->get_num_args() >= 2); + expr_ref_vector args(m), new_args(m), new_out(m); + lazy_mul* lz = nullptr; + rational v0, v1; + unsigned sz, diff = 0; + VERIFY(bv.is_numeral(arg_value, v0, sz)); + VERIFY(bv.is_numeral(mul_value, v1)); + for (diff = 0; diff < sz; ++diff) + if (v0.get_bit(diff) != v1.get_bit(diff)) + break; + SASSERT(diff < sz); + auto set_bits = [&](unsigned j, expr_ref_vector& bits) { + bits.reset(); + for (unsigned i = 0; i < sz; ++i) + bits.push_back(bv.mk_bit2bool(e->get_arg(0), j)); + }; + if (!m_lazymul.find(e, lz)) { + set_bits(0, args); + for (unsigned j = 1; j < e->get_num_args(); ++j) { + new_out.reset(); + set_bits(j, new_args); + m_bb.mk_multiplier(sz, args.data(), new_args.data(), new_out); + new_out.swap(args); + } + lz = alloc(lazy_mul, e, args); + m_lazymul.insert(e, lz); + ctx.push(new_obj_trail(lz)); + ctx.push(insert_obj_map(m_lazymul, e)); + } + if (lz->m_out.size() == lz->m_bits) + return false; + for (unsigned i = lz->m_bits; i <= diff; ++i) { + sat::literal bit1 = mk_literal(lz->m_out.get(i)); + sat::literal bit2 = mk_literal(bv.mk_bit2bool(e, i)); + add_equiv(bit1, bit2); + } + ctx.push(value_trail(lz->m_bits)); + IF_VERBOSE(1, verbose_stream() << "expand lazy mul " << mk_pp(e, m) << " to " << diff << "\n"); + lz->m_bits = diff; + return false; + } + bool solver::check_mul(app* e) { SASSERT(e->get_num_args() >= 2); expr_ref_vector args(m); @@ -96,6 +145,12 @@ namespace bv { if (!check_mul_invertibility(e, args, r1)) return false; +#if 0 + // unsound? + + if (!check_lazy_mul(e, r1, r2)) + return false; +#endif // Some other possible approaches: // algebraic rules: // x*(y+z), and there are nodes for x*y or x*z -> x*(y+z) = x*y + x*z @@ -177,18 +232,17 @@ namespace bv { bool solver::check_mul_zero(app* n, expr_ref_vector const& arg_values, expr* mul_value, expr* arg_value) { SASSERT(mul_value != arg_value); SASSERT(!(bv.is_zero(mul_value) && bv.is_zero(arg_value))); - if (bv.is_zero(arg_value)) { + if (bv.is_zero(arg_value) && false) { unsigned sz = n->get_num_args(); expr_ref_vector args(m, sz, n->get_args()); for (unsigned i = 0; i < sz && !s().inconsistent(); ++i) { - args[i] = arg_value; expr_ref r(m.mk_app(n->get_decl(), args), m); set_delay_internalize(r, internalize_mode::init_bits_only_i); // do not bit-blast this multiplier. args[i] = n->get_arg(i); add_unit(eq_internalize(r, arg_value)); } - IF_VERBOSE(2, verbose_stream() << "delay internalize @" << s().scope_lvl() << "\n"); + IF_VERBOSE(2, verbose_stream() << "delay internalize @" << s().scope_lvl() << " " << mk_pp(n, m) << "\n"); return false; } if (bv.is_zero(mul_value)) { diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp index acec6ff99..1b58fe6a1 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -139,7 +139,9 @@ namespace bv { return true; SASSERT(!n || !n->is_attached_to(get_id())); - bool suppress_args = !reflect() && !m.is_considered_uninterpreted(a->get_decl()); + bool suppress_args = !reflect() + && !m.is_considered_uninterpreted(a->get_decl()) + && !bv.is_int2bv(e) && !bv.is_bv2int(e); if (!n) n = mk_enode(e, suppress_args); @@ -314,7 +316,6 @@ namespace bv { euf::enode* n = bool_var2enode(l.var()); if (!n->is_attached_to(get_id())) mk_var(n); - set_bit_eh(v, l, idx); } @@ -435,7 +436,9 @@ namespace bv { args.push_back(m.mk_ite(b, m_autil.mk_int(power2(i++)), zero)); expr_ref sum(m_autil.mk_add(sz, args.data()), m); sat::literal lit = eq_internalize(n, sum); - add_unit(lit); + m_bv2ints.push_back(expr2enode(n)); + ctx.push(push_back_vector(m_bv2ints)); + add_unit(lit); } void solver::internalize_int2bv(app* n) { @@ -454,6 +457,12 @@ namespace bv { * Create the axioms: * bit2bool(i,n) == ((e div 2^i) mod 2 != 0) * for i = 0,.., sz-1 + * + * Alternative axiomatization: + * e = sum bit2bool(i,n)*2^i + 2^n * (div(e, 2^n)) + * possibly term div(e,2^n) is not correct with respect to adapted semantics? + * if not, use fresh variable or similar. Overall should be much beter. + * Note: based on superb question raised at workshop on 9/1/22. */ void solver::assert_int2bv_axiom(app* n) { expr* e = nullptr; @@ -464,8 +473,8 @@ namespace bv { unsigned sz = bv.get_bv_size(n); numeral mod = power(numeral(2), sz); rhs = m_autil.mk_mod(e, m_autil.mk_int(mod)); - sat::literal eq_lit = eq_internalize(lhs, rhs); - add_unit(eq_lit); + sat::literal eq_lit = eq_internalize(lhs, rhs); + add_unit(eq_lit); expr_ref_vector n_bits(m); get_bits(n_enode, n_bits); @@ -476,8 +485,8 @@ namespace bv { rhs = m_autil.mk_mod(rhs, m_autil.mk_int(2)); rhs = mk_eq(rhs, m_autil.mk_int(1)); lhs = n_bits.get(i); - eq_lit = eq_internalize(lhs, rhs); - add_unit(eq_lit); + eq_lit = eq_internalize(lhs, rhs); + add_unit(eq_lit); } } @@ -534,27 +543,27 @@ namespace bv { internalize_binary(a, bin); } - void solver::internalize_interp(app* n, std::function& ibin, std::function& iun) { + void solver::internalize_interp(app* n, std::function& ibin, std::function& iun) { bv_rewriter_params p(s().params()); expr* arg1 = n->get_arg(0); expr* arg2 = n->get_arg(1); mk_bits(get_th_var(n)); - sat::literal eq_lit; + sat::literal eq_lit; if (p.hi_div0()) { eq_lit = eq_internalize(n, ibin(arg1, arg2)); - add_unit(eq_lit); - } - else { - unsigned sz = bv.get_bv_size(n); - expr_ref zero(bv.mk_numeral(0, sz), m); - sat::literal eqZ = eq_internalize(arg2, zero); - sat::literal eqU = eq_internalize(n, iun(arg1)); - sat::literal eqI = eq_internalize(n, ibin(arg1, arg2)); - add_clause(~eqZ, eqU); - add_clause(eqZ, eqI); - ctx.add_aux(~eqZ, eqU); - ctx.add_aux(eqZ, eqI); - } + add_unit(eq_lit); + } + else { + unsigned sz = bv.get_bv_size(n); + expr_ref zero(bv.mk_numeral(0, sz), m); + sat::literal eqZ = eq_internalize(arg2, zero); + sat::literal eqU = mk_literal(iun(arg1)); + sat::literal eqI = mk_literal(ibin(arg1, arg2)); + add_clause(~eqZ, eqU); + add_clause(eqZ, eqI); + ctx.add_aux(~eqZ, eqU); + ctx.add_aux(eqZ, eqI); + } } void solver::internalize_unary(app* n, std::function& fn) { @@ -574,11 +583,9 @@ namespace bv { init_bits(n, bits); } - void solver::internalize_binary(app* e, std::function& fn) { SASSERT(e->get_num_args() >= 1); - expr_ref_vector bits(m), new_bits(m), arg_bits(m); - + expr_ref_vector bits(m), new_bits(m), arg_bits(m); get_arg_bits(e, 0, bits); for (unsigned i = 1; i < e->get_num_args(); ++i) { arg_bits.reset(); @@ -658,7 +665,7 @@ namespace bv { conc.push_back(arg); expr_ref r(bv.mk_concat(conc), m); mk_bits(get_th_var(e)); - sat::literal eq_lit = eq_internalize(e, r); + sat::literal eq_lit = eq_internalize(e, r); add_unit(eq_lit); } @@ -667,9 +674,8 @@ namespace bv { expr* arg = nullptr; VERIFY(bv.is_bit2bool(n, arg, idx)); euf::enode* argn = expr2enode(arg); - if (!argn->is_attached_to(get_id())) { - mk_var(argn); - } + if (!argn->is_attached_to(get_id())) + mk_var(argn); theory_var v_arg = argn->get_th_var(get_id()); SASSERT(idx < get_bv_size(v_arg)); sat::literal lit = expr2literal(n); @@ -771,7 +777,7 @@ namespace bv { e1 = bv.mk_bit2bool(o1, i); e2 = bv.mk_bit2bool(o2, i); literal eq = eq_internalize(e1, e2); - add_clause(eq, ~oeq); + add_clause(eq, ~oeq); eqs.push_back(~eq); } TRACE("bv", for (auto l : eqs) tout << mk_bounded_pp(literal2expr(l), m) << " "; tout << "\n";); diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index 3612749f4..3a712d4f5 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -212,19 +212,32 @@ namespace bv { return; } euf::enode* n1 = var2enode(eq.v1()); - for (euf::enode* bv2int : euf::enode_class(n1)) { - if (!bv.is_bv2int(bv2int->get_expr())) - continue; + + auto propagate_bv2int = [&](euf::enode* bv2int) { 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)); + theory_var v1 = get_th_var(p); + theory_var v2 = get_th_var(bv2int_arg); + SASSERT(v1 != euf::null_theory_var); + SASSERT(v2 != euf::null_theory_var); + ctx.propagate(p, bv2int_arg, mk_bv2int_justification(v1, v2, n1, p->get_arg(0), bv2int)); break; } } + }; + + if (m_bv2ints.size() < n1->class_size()) { + for (auto* bv2int : m_bv2ints) { + if (bv2int->get_root() == n1->get_root()) + propagate_bv2int(bv2int); + } + } + else { + for (euf::enode* bv2int : euf::enode_class(n1)) { + if (bv.is_bv2int(bv2int->get_expr())) + propagate_bv2int(bv2int); + } } } @@ -282,6 +295,8 @@ namespace bv { ++m_stats.m_num_ne2bit; s().assign(consequent, mk_ne2bit_justification(undef_idx, v1, v2, consequent, antecedent)); } + else if (!get_config().m_bv_eq_axioms) + ; else if (s().at_search_lvl()) { force_push(); assert_ackerman(v1, v2); @@ -368,6 +383,11 @@ namespace bv { r.push_back(b); break; } + case bv_justification::kind_t::bv2int: { + ctx.add_antecedent(c.a, c.b); + ctx.add_antecedent(c.a, c.c); + break; + } } if (!probing && ctx.use_drat()) log_drat(c); @@ -375,19 +395,26 @@ namespace bv { void solver::log_drat(bv_justification const& c) { // introduce dummy literal for equality. - sat::literal leq(s().num_vars() + 1, false); - expr_ref eq(m); - if (c.m_kind != bv_justification::kind_t::bit2ne) { + sat::literal leq1(s().num_vars() + 1, false); + sat::literal leq2(s().num_vars() + 2, false); + expr_ref eq1(m), eq2(m); + if (c.m_kind == bv_justification::kind_t::bv2int) { + eq1 = m.mk_eq(c.a->get_expr(), c.b->get_expr()); + eq2 = m.mk_eq(c.a->get_expr(), c.c->get_expr()); + ctx.set_tmp_bool_var(leq1.var(), eq1); + ctx.set_tmp_bool_var(leq2.var(), eq1); + } + else if (c.m_kind != bv_justification::kind_t::bit2ne) { expr* e1 = var2expr(c.m_v1); expr* e2 = var2expr(c.m_v2); - eq = m.mk_eq(e1, e2); - ctx.drat_eq_def(leq, eq); + eq1 = m.mk_eq(e1, e2); + ctx.set_tmp_bool_var(leq1.var(), eq1); } sat::literal_vector lits; switch (c.m_kind) { case bv_justification::kind_t::eq2bit: - lits.push_back(~leq); + lits.push_back(~leq1); lits.push_back(~c.m_antecedent); lits.push_back(c.m_consequent); break; @@ -396,10 +423,10 @@ namespace bv { lits.push_back(c.m_consequent); break; case bv_justification::kind_t::bit2eq: - get_antecedents(leq, c.to_index(), lits, true); + get_antecedents(leq1, c.to_index(), lits, true); for (auto& lit : lits) lit.neg(); - lits.push_back(leq); + lits.push_back(leq1); break; case bv_justification::kind_t::bit2ne: get_antecedents(c.m_consequent, c.to_index(), lits, true); @@ -407,9 +434,20 @@ namespace bv { lit.neg(); lits.push_back(c.m_consequent); break; + case bv_justification::kind_t::bv2int: + get_antecedents(leq1, c.to_index(), lits, true); + get_antecedents(leq2, c.to_index(), lits, true); + for (auto& lit : lits) + lit.neg(); + lits.push_back(leq1); + lits.push_back(leq2); + break; } ctx.get_drat().add(lits, status()); // TBD, a proper way would be to delete the lemma after use. + ctx.set_tmp_bool_var(leq1.var(), nullptr); + ctx.set_tmp_bool_var(leq2.var(), nullptr); + } void solver::asserted(literal l) { @@ -665,7 +703,9 @@ namespace bv { return out << "bv <- v" << v1 << "[" << cidx << "] != v" << v2 << "[" << cidx << "] " << m_bits[v1][cidx] << " != " << m_bits[v2][cidx]; } case bv_justification::kind_t::ne2bit: - return out << "bv <- " << m_bits[v1] << " != " << m_bits[v2] << " @" << cidx; + return out << "bv <- " << m_bits[v1] << " != " << m_bits[v2] << " @" << cidx; + case bv_justification::kind_t::bv2int: + return out << "bv <- v" << v1 << " == v" << v2 << " <== " << ctx.bpp(c.a) << " == " << ctx.bpp(c.b) << " == " << ctx.bpp(c.c); default: UNREACHABLE(); break; @@ -826,28 +866,41 @@ namespace bv { void* mem = get_region().allocate(bv_justification::get_obj_size()); sat::constraint_base::initialize(mem, this); auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2, c, a); - return sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + auto jst = sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + TRACE("bv", tout << jst << " " << constraint << "\n"); + return jst; } sat::ext_justification_idx solver::mk_bit2eq_justification(theory_var v1, theory_var v2) { void* mem = get_region().allocate(bv_justification::get_obj_size()); sat::constraint_base::initialize(mem, this); auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2); - return constraint->to_index(); + auto jst = constraint->to_index(); + return jst; } sat::justification solver::mk_bit2ne_justification(unsigned idx, sat::literal c) { void* mem = get_region().allocate(bv_justification::get_obj_size()); sat::constraint_base::initialize(mem, this); auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(idx, c); - return sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + auto jst = sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + return jst; } sat::justification solver::mk_ne2bit_justification(unsigned idx, theory_var v1, theory_var v2, sat::literal c, sat::literal a) { void* mem = get_region().allocate(bv_justification::get_obj_size()); sat::constraint_base::initialize(mem, this); auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(idx, v1, v2, c, a); - return sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + auto jst = sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + return jst; + } + + sat::ext_constraint_idx solver::mk_bv2int_justification(theory_var v1, theory_var v2, euf::enode* a, euf::enode* b, euf::enode* c) { + void* mem = get_region().allocate(bv_justification::get_obj_size()); + sat::constraint_base::initialize(mem, this); + auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2, a, b, c); + auto jst = constraint->to_index(); + return jst; } bool solver::assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc) { diff --git a/src/sat/smt/bv_solver.h b/src/sat/smt/bv_solver.h index 4e616a64c..8241415f9 100644 --- a/src/sat/smt/bv_solver.h +++ b/src/sat/smt/bv_solver.h @@ -27,6 +27,15 @@ namespace euf { namespace bv { + struct lazy_mul { + expr_ref_vector m_out; + unsigned m_bits; + lazy_mul(app* a, expr_ref_vector& out): + m_out(out), + m_bits(0) { + } + }; + class solver : public euf::th_euf_solver { typedef rational numeral; typedef euf::theory_var theory_var; @@ -52,13 +61,15 @@ namespace bv { }; struct bv_justification { - enum kind_t { eq2bit, ne2bit, bit2eq, bit2ne }; + enum kind_t { eq2bit, ne2bit, bit2eq, bit2ne, bv2int }; kind_t m_kind; - unsigned m_idx{ UINT_MAX }; - theory_var m_v1{ euf::null_theory_var }; - theory_var m_v2 { euf::null_theory_var }; + unsigned m_idx = UINT_MAX; + theory_var m_v1 = euf::null_theory_var; + theory_var m_v2 = euf::null_theory_var; sat::literal m_consequent; sat::literal m_antecedent; + euf::enode* a, *b, *c; + bv_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) : m_kind(bv_justification::kind_t::eq2bit), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {} bv_justification(theory_var v1, theory_var v2): @@ -67,6 +78,8 @@ namespace bv { m_kind(bv_justification::kind_t::bit2ne), m_idx(idx), m_consequent(c) {} bv_justification(unsigned idx, theory_var v1, theory_var v2, sat::literal c, sat::literal a) : m_kind(bv_justification::kind_t::ne2bit), m_idx(idx), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {} + bv_justification(theory_var v1, theory_var v2, euf::enode* a, euf::enode* b, euf::enode* c): + m_kind(bv_justification::kind_t::bv2int), m_v1(v1), m_v2(v2), a(a), b(b), c(c) {} sat::ext_constraint_idx to_index() const { return sat::constraint_base::mem2base(this); } @@ -80,6 +93,7 @@ namespace bv { sat::ext_justification_idx mk_bit2eq_justification(theory_var v1, theory_var v2); sat::justification mk_bit2ne_justification(unsigned idx, sat::literal c); sat::justification mk_ne2bit_justification(unsigned idx, theory_var v1, theory_var v2, sat::literal c, sat::literal a); + sat::ext_constraint_idx mk_bv2int_justification(theory_var v1, theory_var v2, euf::enode* a, euf::enode* b, euf::enode* c); void log_drat(bv_justification const& c); @@ -210,8 +224,10 @@ namespace bv { literal_vector m_tmp_literals; svector m_prop_queue; unsigned_vector m_prop_queue_lim; - unsigned m_prop_queue_head { 0 }; - sat::literal m_true { sat::null_literal }; + unsigned m_prop_queue_head = 0; + sat::literal m_true = sat::null_literal; + euf::enode_vector m_bv2ints; + obj_map m_lazymul; // internalize void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); } @@ -312,6 +328,7 @@ namespace bv { bool m_cheap_axioms{ true }; bool should_bit_blast(app * n); bool check_delay_internalized(expr* e); + bool check_lazy_mul(app* e, expr* mul_value, expr* arg_value); bool check_mul(app* e); bool check_mul_invertibility(app* n, expr_ref_vector const& arg_values, expr* value); bool check_mul_zero(app* n, expr_ref_vector const& arg_values, expr* value1, expr* value2); diff --git a/src/sat/smt/euf_internalize.cpp b/src/sat/smt/euf_internalize.cpp index 8b8c7da74..c8ae2a7c6 100644 --- a/src/sat/smt/euf_internalize.cpp +++ b/src/sat/smt/euf_internalize.cpp @@ -167,7 +167,7 @@ namespace euf { lit = lit2; } - TRACE("euf", tout << "attach " << v << " " << mk_bounded_pp(e, m) << "\n";); + TRACE("euf", tout << "attach v" << v << " " << mk_bounded_pp(e, m) << "\n";); m_bool_var2expr.reserve(v + 1, nullptr); if (m_bool_var2expr[v] && m_egraph.find(e)) { if (m_egraph.find(e)->bool_var() != v) { @@ -264,7 +264,11 @@ namespace euf { sat::status st = sat::status::th(m_is_redundant, m.get_basic_family_id()); if (sz <= 1) return; - if (sz <= distinct_max_args) { + sort* srt = e->get_arg(0)->get_sort(); + auto sort_sz = srt->get_num_elements(); + if (sort_sz.is_finite() && sort_sz.size() < sz) + s().add_clause(0, nullptr, st); + else if (sz <= distinct_max_args) { for (unsigned i = 0; i < sz; ++i) { for (unsigned j = i + 1; j < sz; ++j) { expr_ref eq = mk_eq(args[i]->get_expr(), args[j]->get_expr()); @@ -274,8 +278,7 @@ namespace euf { } } else { - // dist-f(x_1) = v_1 & ... & dist-f(x_n) = v_n - sort* srt = e->get_arg(0)->get_sort(); + // dist-f(x_1) = v_1 & ... & dist-f(x_n) = v_n SASSERT(!m.is_bool(srt)); sort_ref u(m.mk_fresh_sort("distinct-elems"), m); func_decl_ref f(m.mk_fresh_func_decl("dist-f", "", 1, &srt, u), m); diff --git a/src/sat/smt/euf_proof.cpp b/src/sat/smt/euf_proof.cpp index c072022df..bcbc9439b 100644 --- a/src/sat/smt/euf_proof.cpp +++ b/src/sat/smt/euf_proof.cpp @@ -16,96 +16,26 @@ Author: --*/ #include "sat/smt/euf_solver.h" +#include "ast/ast_util.h" +#include namespace euf { - void solver::init_drat() { - if (!m_drat_initialized) { + void solver::init_proof() { + if (!m_proof_initialized) { get_drat().add_theory(get_id(), symbol("euf")); get_drat().add_theory(m.get_basic_family_id(), symbol("bool")); } - m_drat_initialized = true; - } - - void solver::drat_log_expr1(expr* e) { - if (is_app(e)) { - app* a = to_app(e); - drat_log_decl(a->get_decl()); - std::stringstream strm; - strm << mk_ismt2_func(a->get_decl(), m); - get_drat().def_begin('e', e->get_id(), strm.str()); - for (expr* arg : *a) - get_drat().def_add_arg(arg->get_id()); - get_drat().def_end(); + if (!m_proof_out && s().get_config().m_drat && + (get_config().m_lemmas2console || s().get_config().m_smt_proof.is_non_empty_string())) { + TRACE("euf", tout << "init-proof\n"); + m_proof_out = alloc(std::ofstream, s().get_config().m_smt_proof.str(), std::ios_base::out); + if (get_config().m_lemmas2console) + get_drat().set_clause_eh(*this); + if (s().get_config().m_smt_proof.is_non_empty_string()) + get_drat().set_clause_eh(*this); } - else if (is_var(e)) { - var* v = to_var(e); - get_drat().def_begin('v', v->get_id(), "" + mk_pp(e->get_sort(), m)); - get_drat().def_add_arg(v->get_idx()); - get_drat().def_end(); - } - else if (is_quantifier(e)) { - quantifier* q = to_quantifier(e); - std::stringstream strm; - strm << "(" << (is_forall(q) ? "forall" : (is_exists(q) ? "exists" : "lambda")); - for (unsigned i = 0; i < q->get_num_decls(); ++i) - strm << " (" << q->get_decl_name(i) << " " << mk_pp(q->get_decl_sort(i), m) << ")"; - strm << ")"; - get_drat().def_begin('q', q->get_id(), strm.str()); - get_drat().def_add_arg(q->get_expr()->get_id()); - get_drat().def_end(); - } - else - UNREACHABLE(); - m_drat_asts.insert(e); - push(insert_obj_trail(m_drat_asts, e)); - } - - void solver::drat_log_expr(expr* e) { - if (m_drat_asts.contains(e)) - return; - ptr_vector::scoped_stack _sc(m_drat_todo); - m_drat_todo.push_back(e); - while (!m_drat_todo.empty()) { - e = m_drat_todo.back(); - unsigned sz = m_drat_todo.size(); - if (is_app(e)) - for (expr* arg : *to_app(e)) - if (!m_drat_asts.contains(arg)) - m_drat_todo.push_back(arg); - if (is_quantifier(e)) { - expr* arg = to_quantifier(e)->get_expr(); - if (!m_drat_asts.contains(arg)) - m_drat_todo.push_back(arg); - } - if (m_drat_todo.size() != sz) - continue; - if (!m_drat_asts.contains(e)) - drat_log_expr1(e); - m_drat_todo.pop_back(); - } - } - - void solver::drat_bool_def(sat::bool_var v, expr* e) { - if (!use_drat()) - return; - drat_log_expr(e); - get_drat().bool_def(v, e->get_id()); - } - - - void solver::drat_log_decl(func_decl* f) { - if (f->get_family_id() != null_family_id) - return; - if (m_drat_asts.contains(f)) - return; - m_drat_asts.insert(f); - push(insert_obj_trail< ast>(m_drat_asts, f)); - std::ostringstream strm; - smt2_pp_environment_dbg env(m); - ast_smt2_pp(strm, f, env); - get_drat().def_begin('f', f->get_small_id(), strm.str()); - get_drat().def_end(); + m_proof_initialized = true; } /** @@ -144,16 +74,20 @@ namespace euf { } } + void solver::set_tmp_bool_var(bool_var b, expr* e) { + m_bool_var2expr.setx(b, e, nullptr); + } + void solver::log_justification(literal l, th_explain const& jst) { literal_vector lits; - unsigned nv = s().num_vars(); expr_ref_vector eqs(m); + unsigned nv = s().num_vars(); auto add_lit = [&](enode_pair const& eq) { + unsigned v = nv; ++nv; - literal lit(nv, false); eqs.push_back(m.mk_eq(eq.first->get_expr(), eq.second->get_expr())); - drat_eq_def(lit, eqs.back()); - return lit; + set_tmp_bool_var(v, eqs.back()); + return literal(v, false); }; for (auto lit : euf::th_explain::lits(jst)) @@ -167,18 +101,137 @@ namespace euf { if (jst.eq_consequent().first != nullptr) lits.push_back(add_lit(jst.eq_consequent())); get_drat().add(lits, sat::status::th(m_is_redundant, jst.ext().get_id(), jst.get_pragma())); + for (unsigned i = s().num_vars(); i < nv; ++i) + set_tmp_bool_var(i, nullptr); } - void solver::drat_eq_def(literal lit, expr* eq) { - expr *a = nullptr, *b = nullptr; - VERIFY(m.is_eq(eq, a, b)); - drat_log_expr(a); - drat_log_expr(b); - get_drat().def_begin('e', eq->get_id(), std::string("=")); - get_drat().def_add_arg(a->get_id()); - get_drat().def_add_arg(b->get_id()); - get_drat().def_end(); - get_drat().bool_def(lit.var(), eq->get_id()); + void solver::on_clause(unsigned n, literal const* lits, sat::status st) { + TRACE("euf", tout << "on-clause " << n << "\n"); + on_lemma(n, lits, st); + on_proof(n, lits, st); + } + + void solver::on_proof(unsigned n, literal const* lits, sat::status st) { + if (!m_proof_out) + return; + flet _display_all_decls(m_display_all_decls, true); + std::ostream& out = *m_proof_out; + if (!visit_clause(out, n, lits)) + return; + if (st.is_asserted()) + display_redundant(out, n, lits, status2proof_hint(st)); + else if (st.is_deleted()) + display_deleted(out, n, lits); + else if (st.is_redundant()) + display_redundant(out, n, lits, status2proof_hint(st)); + else if (st.is_input()) + display_assume(out, n, lits); + else + UNREACHABLE(); + out.flush(); + } + + void solver::on_lemma(unsigned n, literal const* lits, sat::status st) { + if (!get_config().m_lemmas2console) + return; + if (!st.is_redundant() && !st.is_asserted()) + return; + + std::ostream& out = std::cout; + if (!visit_clause(out, n, lits)) + return; + std::function ppth = [&](int th) { + return m.get_family_name(th); + }; + if (!st.is_sat()) + out << "; " << sat::status_pp(st, ppth) << "\n"; + + display_assert(out, n, lits); + } + + void solver::on_instantiation(unsigned n, sat::literal const* lits, unsigned k, euf::enode* const* bindings) { + std::ostream& out = std::cout; + for (unsigned i = 0; i < k; ++i) + visit_expr(out, bindings[i]->get_expr()); + VERIFY(visit_clause(out, n, lits)); + out << "(instantiate"; + display_literals(out, n, lits); + for (unsigned i = 0; i < k; ++i) + display_expr(out << " :binding ", bindings[i]->get_expr()); + out << ")\n"; + } + + bool solver::visit_clause(std::ostream& out, unsigned n, literal const* lits) { + for (unsigned i = 0; i < n; ++i) { + expr* e = bool_var2expr(lits[i].var()); + if (!e) + return false; + visit_expr(out, e); + } + return true; + } + + void solver::display_assert(std::ostream& out, unsigned n, literal const* lits) { + display_literals(out << "(assert (or", n, lits) << "))\n"; + } + + void solver::display_assume(std::ostream& out, unsigned n, literal const* lits) { + display_literals(out << "(assume", n, lits) << ")\n"; + } + + void solver::display_redundant(std::ostream& out, unsigned n, literal const* lits, expr* proof_hint) { + if (proof_hint) + visit_expr(out, proof_hint); + display_hint(display_literals(out << "(learn", n, lits), proof_hint) << ")\n"; + } + + void solver::display_deleted(std::ostream& out, unsigned n, literal const* lits) { + display_literals(out << "(del", n, lits) << ")\n"; + } + + std::ostream& solver::display_hint(std::ostream& out, expr* proof_hint) { + if (proof_hint) + return display_expr(out << " ", proof_hint); + else + return out; + } + + expr_ref solver::status2proof_hint(sat::status st) { + if (st.is_sat()) + return expr_ref(m.mk_const("rup", m.mk_proof_sort()), m); // provable by reverse unit propagation + auto* h = reinterpret_cast(st.get_hint()); + if (!h) + return expr_ref(m); + + expr* e = h->get_hint(*this); + if (e) + return expr_ref(e, m); + + return expr_ref(m); + } + + std::ostream& solver::display_literals(std::ostream& out, unsigned n, literal const* lits) { + for (unsigned i = 0; i < n; ++i) { + expr* e = bool_var2expr(lits[i].var()); + if (lits[i].sign()) + display_expr(out << " (not ", e) << ")"; + else + display_expr(out << " ", e); + } + return out; + } + + void solver::visit_expr(std::ostream& out, expr* e) { + m_clause_visitor.collect(e); + if (m_display_all_decls) + m_clause_visitor.display_decls(out); + else + m_clause_visitor.display_skolem_decls(out); + m_clause_visitor.define_expr(out, e); + } + + std::ostream& solver::display_expr(std::ostream& out, expr* e) { + return m_clause_visitor.display_expr_def(out, e); } } diff --git a/src/sat/smt/euf_proof_checker.cpp b/src/sat/smt/euf_proof_checker.cpp new file mode 100644 index 000000000..41f627914 --- /dev/null +++ b/src/sat/smt/euf_proof_checker.cpp @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + euf_proof_checker.cpp + +Abstract: + + Plugin manager for checking EUF proofs + +Author: + + Nikolaj Bjorner (nbjorner) 2020-08-25 + +--*/ + +#include "ast/ast_pp.h" +#include "sat/smt/euf_proof_checker.h" +#include "sat/smt/arith_proof_checker.h" + +namespace euf { + + proof_checker::proof_checker(ast_manager& m): + m(m) { + arith::proof_checker* apc = alloc(arith::proof_checker, m); + m_plugins.push_back(apc); + apc->register_plugins(*this); + (void)m; + } + + proof_checker::~proof_checker() {} + + void proof_checker::register_plugin(symbol const& rule, proof_checker_plugin* p) { + m_map.insert(rule, p); + } + + bool proof_checker::check(expr_ref_vector const& clause, expr* e, expr_ref_vector& units) { + if (!e || !is_app(e)) + return false; + units.reset(); + app* a = to_app(e); + proof_checker_plugin* p = nullptr; + if (m_map.find(a->get_decl()->get_name(), p)) + return p->check(clause, a, units); + return false; + } + +} + diff --git a/src/sat/smt/euf_proof_checker.h b/src/sat/smt/euf_proof_checker.h new file mode 100644 index 000000000..464d90559 --- /dev/null +++ b/src/sat/smt/euf_proof_checker.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + euf_proof_checker.h + +Abstract: + + Plugin manager for checking EUF proofs + +Author: + + Nikolaj Bjorner (nbjorner) 2022-08-25 + +--*/ +#pragma once + +#include "util/map.h" +#include "util/scoped_ptr_vector.h" +#include "ast/ast.h" + +namespace euf { + + class proof_checker; + + class proof_checker_plugin { + public: + virtual ~proof_checker_plugin() {} + virtual bool check(expr_ref_vector const& clause, app* jst, expr_ref_vector& units) = 0; + virtual void register_plugins(proof_checker& pc) = 0; + }; + + class proof_checker { + ast_manager& m; + scoped_ptr_vector m_plugins; + map m_map; + public: + proof_checker(ast_manager& m); + ~proof_checker(); + void register_plugin(symbol const& rule, proof_checker_plugin*); + bool check(expr_ref_vector const& clause, expr* e, expr_ref_vector& units); + }; + +} + diff --git a/src/sat/smt/euf_solver.cpp b/src/sat/smt/euf_solver.cpp index 12e066437..806509e4a 100644 --- a/src/sat/smt/euf_solver.cpp +++ b/src/sat/smt/euf_solver.cpp @@ -49,7 +49,8 @@ namespace euf { m_lookahead(nullptr), m_to_m(&m), m_to_si(&si), - m_values(m) + m_values(m), + m_clause_visitor(m) { updt_params(p); m_relevancy.set_enabled(get_config().m_relevancy_lvl > 2); @@ -347,8 +348,7 @@ namespace euf { if (m_relevancy.enabled()) m_relevancy.propagate(); if (m_egraph.inconsistent()) { - unsigned lvl = s().scope_lvl(); - s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index())); + set_conflict(conflict_constraint().to_index()); return true; } bool propagated1 = false; @@ -488,7 +488,8 @@ namespace euf { }; if (merge_shared_bools()) cont = true; - for (auto* e : m_solvers) { + for (unsigned i = 0; i < m_solvers.size(); ++i) { + auto* e = m_solvers[i]; if (!m.inc()) return sat::check_result::CR_GIVEUP; if (e == m_qsolver) @@ -519,7 +520,7 @@ namespace euf { bool merged = false; for (unsigned i = m_egraph.nodes().size(); i-- > 0; ) { euf::enode* n = m_egraph.nodes()[i]; - if (!is_shared(n) || !m.is_bool(n->get_expr())) + if (!m.is_bool(n->get_expr()) || !is_shared(n)) continue; if (n->value() == l_true && !m.is_true(n->get_root()->get_expr())) { m_egraph.merge(n, mk_true(), to_ptr(sat::literal(n->bool_var()))); @@ -616,15 +617,17 @@ namespace euf { else lit = si.internalize(e, false); VERIFY(lit.var() == v); - if (!m_egraph.find(e) && (!m.is_iff(e) && !m.is_or(e) && !m.is_and(e) && !m.is_not(e))) { + if (!m_egraph.find(e) && !m.is_iff(e) && !m.is_or(e) && !m.is_and(e) && !m.is_not(e) && !m.is_implies(e) && !m.is_xor(e)) { ptr_buffer args; if (is_app(e)) for (expr* arg : *to_app(e)) args.push_back(e_internalize(arg)); + internalize(e, true); if (!m_egraph.find(e)) mk_enode(e, args.size(), args.data()); } - attach_lit(lit, e); + else + attach_lit(lit, e); } if (relevancy_enabled()) @@ -778,7 +781,7 @@ namespace euf { } for (auto const& thv : enode_th_vars(n)) { auto* th = m_id2solver.get(thv.get_id(), nullptr); - if (th && !th->is_fixed(thv.get_var(), val, explain)) + if (th && th->is_fixed(thv.get_var(), val, explain)) return true; } return false; @@ -863,7 +866,13 @@ namespace euf { out << "bool-vars\n"; for (unsigned v : m_var_trail) { expr* e = m_bool_var2expr[v]; - out << v << (is_relevant(v)?"":"n") << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1) << "\n"; + out << v << (is_relevant(v)?"":"n") << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1); + euf::enode* n = m_egraph.find(e); + if (n) { + for (auto const& th : enode_th_vars(n)) + out << " " << m_id2solver[th.get_id()]->name(); + } + out << "\n"; } for (auto* e : m_solvers) e->display(out); @@ -1067,10 +1076,7 @@ namespace euf { user_propagator::fresh_eh_t& fresh_eh) { m_user_propagator = alloc(user_solver::solver, *this); m_user_propagator->add(ctx, push_eh, pop_eh, fresh_eh); - for (unsigned i = m_scopes.size(); i-- > 0; ) - m_user_propagator->push(); - m_solvers.push_back(m_user_propagator); - m_id2solver.setx(m_user_propagator->get_id(), m_user_propagator, nullptr); + add_solver(m_user_propagator); } bool solver::watches_fixed(enode* n) const { diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h index f7129f3c5..4dc217b66 100644 --- a/src/sat/smt/euf_solver.h +++ b/src/sat/smt/euf_solver.h @@ -60,11 +60,10 @@ namespace euf { std::ostream& display(std::ostream& out) const; }; - class solver : public sat::extension, public th_internalizer, public th_decompile { + class solver : public sat::extension, public th_internalizer, public th_decompile, public sat::clause_eh { typedef top_sort deps_t; friend class ackerman; class user_sort; - // friend class sat::ba_solver; struct stats { unsigned m_ackerman; unsigned m_final_checks; @@ -161,7 +160,6 @@ namespace euf { void collect_dependencies(user_sort& us, deps_t& deps); void values2model(deps_t const& deps, model_ref& mdl); void validate_model(model& mdl); - void display_validation_failure(std::ostream& out, model& mdl, enode* n); // solving void propagate_literals(); @@ -175,19 +173,22 @@ namespace euf { void log_antecedents(std::ostream& out, literal l, literal_vector const& r); void log_antecedents(literal l, literal_vector const& r); void log_justification(literal l, th_explain const& jst); - void drat_log_decl(func_decl* f); - void drat_log_expr1(expr* n); - ptr_vector m_drat_todo; - obj_hashtable m_drat_asts; - bool m_drat_initialized{ false }; - void init_drat(); + + bool m_proof_initialized = false; + void init_proof(); + ast_pp_util m_clause_visitor; + bool m_display_all_decls = false; + void on_clause(unsigned n, literal const* lits, sat::status st) override; + void on_lemma(unsigned n, literal const* lits, sat::status st); + void on_proof(unsigned n, literal const* lits, sat::status st); + std::ostream& display_literals(std::ostream& out, unsigned n, sat::literal const* lits); + void display_assume(std::ostream& out, unsigned n, literal const* lits); + void display_redundant(std::ostream& out, unsigned n, literal const* lits, expr* proof_hint); + void display_deleted(std::ostream& out, unsigned n, literal const* lits); + std::ostream& display_hint(std::ostream& out, expr* proof_hint); + expr_ref status2proof_hint(sat::status st); // relevancy - //bool_vector m_relevant_expr_ids; - //bool_vector m_relevant_visited; - //ptr_vector m_relevant_todo; - //void init_relevant_expr_ids(); - //void push_relevant(sat::bool_var v); bool is_propagated(sat::literal lit); // invariant void check_eqc_bool_assignment() const; @@ -340,11 +341,16 @@ namespace euf { // proof - bool use_drat() { return s().get_config().m_drat && (init_drat(), true); } + bool use_drat() { return s().get_config().m_drat && (init_proof(), true); } sat::drat& get_drat() { return s().get_drat(); } - void drat_bool_def(sat::bool_var v, expr* n); - void drat_eq_def(sat::literal lit, expr* eq); - void drat_log_expr(expr* n); + + void set_tmp_bool_var(sat::bool_var b, expr* e); + bool visit_clause(std::ostream& out, unsigned n, literal const* lits); + void display_assert(std::ostream& out, unsigned n, literal const* lits); + void visit_expr(std::ostream& out, expr* e); + std::ostream& display_expr(std::ostream& out, expr* e); + void on_instantiation(unsigned n, sat::literal const* lits, unsigned k, euf::enode* const* bindings); + scoped_ptr m_proof_out; // decompile bool extract_pb(std::function& card, @@ -403,6 +409,7 @@ namespace euf { obj_map const& values2root(); void model_updated(model_ref& mdl); expr* node2value(enode* n) const; + void display_validation_failure(std::ostream& out, model& mdl, enode* n); // diagnostics func_decl_ref_vector const& unhandled_functions() { return m_unhandled_functions; } diff --git a/src/sat/smt/pb_solver.cpp b/src/sat/smt/pb_solver.cpp index 13d3e02e4..1c762cb3f 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -1430,10 +1430,9 @@ namespace pb { IF_VERBOSE(0, verbose_stream() << *c << "\n"); VERIFY(c->well_formed()); if (m_solver && m_solver->get_config().m_drat) { - std::function fn = [&](std::ostream& out) { - out << "c ba constraint " << *c << " 0\n"; - }; - m_solver->get_drat().log_adhoc(fn); + auto * out = s().get_drat().out(); + if (out) + *out << "c ba constraint " << *c << " 0\n"; } } diff --git a/src/sat/smt/q_ematch.cpp b/src/sat/smt/q_ematch.cpp index 29db324f9..490bce46e 100644 --- a/src/sat/smt/q_ematch.cpp +++ b/src/sat/smt/q_ematch.cpp @@ -372,16 +372,21 @@ namespace q { } void ematch::propagate(bool is_conflict, unsigned idx, sat::ext_justification_idx j_idx) { - if (is_conflict) { + if (is_conflict) ++m_stats.m_num_conflicts; - ctx.set_conflict(j_idx); - } - else { + else ++m_stats.m_num_propagations; - auto& j = justification::from_index(j_idx); - auto lit = instantiate(j.m_clause, j.m_binding, j.m_clause[idx]); - ctx.propagate(lit, j_idx); - } + + auto& j = justification::from_index(j_idx); + sat::literal_vector lits; + lits.push_back(~j.m_clause.m_literal); + for (unsigned i = 0; i < j.m_clause.size(); ++i) + lits.push_back(instantiate(j.m_clause, j.m_binding, j.m_clause[i])); + m_qs.log_instantiation(lits, &j); + euf::th_proof_hint* ph = nullptr; + if (ctx.use_drat()) + ph = q_proof_hint::mk(ctx, j.m_clause.size(), j.m_binding); + m_qs.add_clause(lits, ph); } bool ematch::flush_prop_queue() { @@ -408,6 +413,7 @@ namespace q { void ematch::add_instantiation(clause& c, binding& b, sat::literal lit) { m_evidence.reset(); ctx.propagate(lit, mk_justification(UINT_MAX, c, b.nodes())); + m_qs.log_instantiation(~c.m_literal, lit); } sat::literal ematch::instantiate(clause& c, euf::enode* const* binding, lit const& l) { diff --git a/src/sat/smt/q_eval.cpp b/src/sat/smt/q_eval.cpp index a87c5ec0d..ae79dbeee 100644 --- a/src/sat/smt/q_eval.cpp +++ b/src/sat/smt/q_eval.cpp @@ -47,21 +47,21 @@ namespace q { unsigned lim = m_indirect_nodes.size(); lit l = c[i]; lbool cmp = compare(n, binding, l.lhs, l.rhs, evidence); + TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is " << cmp << "\n";); switch (cmp) { - case l_false: + case l_false: m_indirect_nodes.shrink(lim); if (!l.sign) break; c.m_watch = i; return l_true; - case l_true: + case l_true: m_indirect_nodes.shrink(lim); if (l.sign) - break; + break; c.m_watch = i; return l_true; case l_undef: - TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is undef\n";); if (idx != UINT_MAX) { idx = UINT_MAX; return l_undef; diff --git a/src/sat/smt/q_mam.cpp b/src/sat/smt/q_mam.cpp index fa8b2bc26..91f19806a 100644 --- a/src/sat/smt/q_mam.cpp +++ b/src/sat/smt/q_mam.cpp @@ -1961,7 +1961,7 @@ namespace q { for (unsigned i = 0; i < num_args; i++) m_args[i] = m_registers[pc->m_iregs[i]]->get_root(); for (enode* n : euf::enode_class(r)) { - if (n->get_decl() == f) { + if (n->get_decl() == f && num_args == n->num_args()) { unsigned i = 0; for (; i < num_args; i++) { if (n->get_arg(i)->get_root() != m_args[i]) diff --git a/src/sat/smt/q_mam.h b/src/sat/smt/q_mam.h index c396a319b..9fc6d18f1 100644 --- a/src/sat/smt/q_mam.h +++ b/src/sat/smt/q_mam.h @@ -45,7 +45,7 @@ namespace q { static mam * mk(euf::solver& ctx, ematch& em); - virtual ~mam() {} + virtual ~mam() = default; virtual void add_pattern(quantifier * q, app * mp) = 0; diff --git a/src/sat/smt/q_mbi.cpp b/src/sat/smt/q_mbi.cpp index af49f4eda..f2caa2a3e 100644 --- a/src/sat/smt/q_mbi.cpp +++ b/src/sat/smt/q_mbi.cpp @@ -68,10 +68,21 @@ namespace q { } } m_max_cex += ctx.get_config().m_mbqi_max_cexs; - for (auto const& [qlit, fml, generation] : m_instantiations) { + for (auto const& [qlit, fml, inst, generation] : m_instantiations) { euf::solver::scoped_generation sg(ctx, generation + 1); sat::literal lit = ctx.mk_literal(fml); - m_qs.add_clause(~qlit, ~lit); + euf::th_proof_hint* ph = nullptr; + if (!inst.empty()) { + ph = q_proof_hint::mk(ctx, inst.size(), inst.data()); + sat::literal_vector lits; + lits.push_back(~qlit); + lits.push_back(~lit); + m_qs.add_clause(lits, ph); + } + else { + m_qs.add_clause(~qlit, ~lit); + } + m_qs.log_instantiation(~qlit, ~lit); } m_instantiations.reset(); if (result != l_true) @@ -223,10 +234,31 @@ namespace q { TRACE("q", tout << "project: " << proj << "\n";); IF_VERBOSE(11, verbose_stream() << "mbi:\n" << mk_pp(q, m) << "\n" << proj << "\n"); ++m_stats.m_num_instantiations; - unsigned generation = ctx.get_max_generation(proj); - m_instantiations.push_back(instantiation_t(qlit, proj, generation)); + unsigned generation = ctx.get_max_generation(proj); + expr_ref_vector inst = extract_binding(q); + m_instantiations.push_back(instantiation_t(qlit, proj, inst, generation)); } + expr_ref_vector mbqi::extract_binding(quantifier* q) { + if (!m_defs.empty()) { + expr_safe_replace sub(m); + for (unsigned i = m_defs.size(); i-- > 0; ) { + sub(m_defs[i].term); + sub.insert(m_defs[i].var, m_defs[i].term); + } + q_body* qb = q2body(q); + expr_ref_vector inst(m); + for (expr* v : qb->vars) { + expr_ref t(m); + sub(v, t); + inst.push_back(t); + } + return inst; + } + return expr_ref_vector(m); + } + + void mbqi::add_universe_restriction(q_body& qb) { for (app* v : qb.vars) { sort* s = v->get_sort(); @@ -283,6 +315,7 @@ namespace q { expr_ref_vector fmls(qb.vbody); app_ref_vector vars(qb.vars); bool fmls_extracted = false; + m_defs.reset(); TRACE("q", tout << "Project\n"; tout << fmls << "\n"; @@ -313,16 +346,22 @@ namespace q { fmls_extracted = true; } if (!p) - continue; - if (!(*p)(*m_model, vars, fmls)) - return expr_ref(nullptr, m); + continue; + if (ctx.use_drat()) { + if (!p->project(*m_model, vars, fmls, m_defs)) + return expr_ref(m); + } + else if (!(*p)(*m_model, vars, fmls)) + return expr_ref(m); } for (app* v : vars) { expr_ref term(m); expr_ref val = (*m_model)(v); val = m_model->unfold_as_array(val); term = replace_model_value(val); - rep.insert(v, term); + rep.insert(v, term); + if (ctx.use_drat()) + m_defs.push_back(mbp::def(expr_ref(v, m), term)); eqs.push_back(m.mk_eq(v, val)); } rep(fmls); @@ -596,8 +635,8 @@ namespace q { void mbqi::collect_statistics(statistics& st) const { if (m_solver) m_solver->collect_statistics(st); - st.update("q-num-instantiations", m_stats.m_num_instantiations); - st.update("q-num-checks", m_stats.m_num_checks); + st.update("q mbi instantiations", m_stats.m_num_instantiations); + st.update("q mbi num checks", m_stats.m_num_checks); } } diff --git a/src/sat/smt/q_mbi.h b/src/sat/smt/q_mbi.h index c7dc4a551..2cc5655bf 100644 --- a/src/sat/smt/q_mbi.h +++ b/src/sat/smt/q_mbi.h @@ -66,15 +66,17 @@ namespace q { scoped_ptr_vector> m_values; scoped_ptr_vector m_plugins; obj_map m_q2body; - unsigned m_max_cex{ 1 }; - unsigned m_max_quick_check_rounds { 100 }; - unsigned m_max_unbounded_equalities { 10 }; - unsigned m_max_choose_candidates { 10 }; - unsigned m_generation_bound{ UINT_MAX }; - unsigned m_generation_max { UINT_MAX }; - typedef std::tuple instantiation_t; + unsigned m_max_cex = 1; + unsigned m_max_quick_check_rounds = 100; + unsigned m_max_unbounded_equalities = 10; + unsigned m_max_choose_candidates = 10; + unsigned m_generation_bound = UINT_MAX; + unsigned m_generation_max = UINT_MAX; + typedef std::tuple instantiation_t; vector m_instantiations; + vector m_defs; + expr_ref_vector extract_binding(quantifier* q); void restrict_to_universe(expr * sk, ptr_vector const & universe); // void register_value(expr* e); expr_ref replace_model_value(expr* e); diff --git a/src/sat/smt/q_model_fixer.h b/src/sat/smt/q_model_fixer.h index 9653268e6..2c16504c6 100644 --- a/src/sat/smt/q_model_fixer.h +++ b/src/sat/smt/q_model_fixer.h @@ -43,7 +43,7 @@ namespace q { ast_manager& m; public: projection_function(ast_manager& m) : m(m) {} - virtual ~projection_function() {} + virtual ~projection_function() = default; virtual expr* mk_lt(expr* a, expr* b) = 0; expr* mk_le(expr* a, expr* b) { return m.mk_not(mk_lt(b, a)); } virtual bool operator()(expr* a, expr* b) const = 0; diff --git a/src/sat/smt/q_solver.cpp b/src/sat/smt/q_solver.cpp index 7c1c19040..0a8ca5085 100644 --- a/src/sat/smt/q_solver.cpp +++ b/src/sat/smt/q_solver.cpp @@ -24,6 +24,7 @@ Author: #include "sat/smt/euf_solver.h" #include "sat/smt/sat_th.h" #include "qe/lite/qe_lite.h" +#include namespace q { @@ -356,4 +357,43 @@ namespace q { m_ematch.get_antecedents(l, idx, r, probing); } + void solver::log_instantiation(unsigned n, sat::literal const* lits, justification* j) { + TRACE("q", for (unsigned i = 0; i < n; ++i) tout << literal2expr(lits[i]) << "\n";); + if (get_config().m_instantiations2console) { + ctx.on_instantiation(n, lits, j ? j->m_clause.num_decls() : 0, j ? j->m_binding : nullptr); + } + } + + q_proof_hint* q_proof_hint::mk(euf::solver& s, unsigned n, euf::enode* const* bindings) { + auto* mem = s.get_region().allocate(q_proof_hint::get_obj_size(n)); + q_proof_hint* ph = new (mem) q_proof_hint(); + ph->m_num_bindings = n; + for (unsigned i = 0; i < n; ++i) + ph->m_bindings[i] = bindings[i]->get_expr(); + return ph; + } + + q_proof_hint* q_proof_hint::mk(euf::solver& s, unsigned n, expr* const* bindings) { + auto* mem = s.get_region().allocate(q_proof_hint::get_obj_size(n)); + q_proof_hint* ph = new (mem) q_proof_hint(); + ph->m_num_bindings = n; + for (unsigned i = 0; i < n; ++i) + ph->m_bindings[i] = bindings[i]; + return ph; + } + + expr* q_proof_hint::get_hint(euf::solver& s) const { + ast_manager& m = s.get_manager(); + expr_ref_vector args(m); + sort_ref_vector sorts(m); + for (unsigned i = 0; i < m_num_bindings; ++i) { + args.push_back(m_bindings[i]); + sorts.push_back(args.back()->get_sort()); + } + sort* range = m.mk_proof_sort(); + func_decl* d = m.mk_func_decl(symbol("inst"), args.size(), sorts.data(), range); + expr* r = m.mk_app(d, args); + return r; + } + } diff --git a/src/sat/smt/q_solver.h b/src/sat/smt/q_solver.h index 32025888b..ee2e47111 100644 --- a/src/sat/smt/q_solver.h +++ b/src/sat/smt/q_solver.h @@ -29,6 +29,16 @@ namespace euf { namespace q { + struct q_proof_hint : public euf::th_proof_hint { + unsigned m_num_bindings; + expr* m_bindings[0]; + q_proof_hint() {} + static size_t get_obj_size(unsigned num_bindings) { return sizeof(q_proof_hint) + num_bindings*sizeof(expr*); } + static q_proof_hint* mk(euf::solver& s, unsigned n, euf::enode* const* bindings); + static q_proof_hint* mk(euf::solver& s, unsigned n, expr* const* bindings); + expr* get_hint(euf::solver& s) const override; + }; + class solver : public euf::th_euf_solver { typedef obj_map flat_table; @@ -88,5 +98,9 @@ namespace q { sat::literal_vector const& universal() const { return m_universal; } quantifier* flatten(quantifier* q); + void log_instantiation(sat::literal q, sat::literal i, justification* j = nullptr) { sat::literal lits[2] = { q, i }; log_instantiation(2, lits, j); } + void log_instantiation(sat::literal_vector const& lits, justification* j) { log_instantiation(lits.size(), lits.data(), j); } + void log_instantiation(unsigned n, sat::literal const* lits, justification* j); + }; } diff --git a/src/sat/smt/sat_internalizer.h b/src/sat/smt/sat_internalizer.h index 43413a893..e7d0d9b43 100644 --- a/src/sat/smt/sat_internalizer.h +++ b/src/sat/smt/sat_internalizer.h @@ -21,7 +21,7 @@ Author: namespace sat { class sat_internalizer { public: - virtual ~sat_internalizer() {} + virtual ~sat_internalizer() = default; virtual bool is_bool_op(expr* e) const = 0; virtual literal internalize(expr* e, bool learned) = 0; virtual bool_var to_bool_var(expr* e) = 0; diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp index 7f4ff2f4d..3267f0940 100644 --- a/src/sat/smt/sat_th.cpp +++ b/src/sat/smt/sat_th.cpp @@ -125,7 +125,7 @@ namespace euf { pop_core(n); } - sat::status th_euf_solver::mk_status(sat::proof_hint const* ps) { + sat::status th_euf_solver::mk_status(th_proof_hint const* ps) { return sat::status::th(m_is_redundant, get_id(), ps); } @@ -149,7 +149,7 @@ namespace euf { return add_clause(2, lits); } - bool th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::proof_hint const* ps) { + bool th_euf_solver::add_clause(sat::literal a, sat::literal b, th_proof_hint const* ps) { sat::literal lits[2] = { a, b }; return add_clause(2, lits, ps); } @@ -164,7 +164,7 @@ namespace euf { return add_clause(4, lits); } - bool th_euf_solver::add_clause(unsigned n, sat::literal* lits, sat::proof_hint const* ps) { + bool th_euf_solver::add_clause(unsigned n, sat::literal* lits, th_proof_hint const* ps) { bool was_true = false; for (unsigned i = 0; i < n; ++i) was_true |= is_true(lits[i]); @@ -226,13 +226,14 @@ namespace euf { return ctx.s().rand()(); } - size_t th_explain::get_obj_size(unsigned num_lits, unsigned num_eqs, sat::proof_hint const* pma) { - return sat::constraint_base::obj_size(sizeof(th_explain) + sizeof(sat::literal) * num_lits + sizeof(enode_pair) * num_eqs + (pma?pma->to_string().length()+1:1)); + size_t th_explain::get_obj_size(unsigned num_lits, unsigned num_eqs) { + return sat::constraint_base::obj_size(sizeof(th_explain) + sizeof(sat::literal) * num_lits + sizeof(enode_pair) * num_eqs); } - th_explain::th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& p, sat::proof_hint const* pma) { + th_explain::th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& p, th_proof_hint const* pma) { m_consequent = c; m_eq = p; + m_proof_hint = pma; m_num_literals = n_lits; m_num_eqs = n_eqs; char * base_ptr = reinterpret_cast(this) + sizeof(th_explain); @@ -244,33 +245,24 @@ namespace euf { m_eqs = reinterpret_cast(base_ptr); for (i = 0; i < n_eqs; ++i) m_eqs[i] = eqs[i]; - base_ptr += sizeof(enode_pair) * n_eqs; - m_pragma = reinterpret_cast(base_ptr); - i = 0; - if (pma) { - std::string s = pma->to_string(); - for (i = 0; s[i]; ++i) - m_pragma[i] = s[i]; - } - m_pragma[i] = 0; } - th_explain* th_explain::mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, sat::proof_hint const* pma) { + th_explain* th_explain::mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, th_proof_hint const* pma) { region& r = th.ctx.get_region(); - void* mem = r.allocate(get_obj_size(n_lits, n_eqs, pma)); + void* mem = r.allocate(get_obj_size(n_lits, n_eqs)); sat::constraint_base::initialize(mem, &th); - return new (sat::constraint_base::ptr2mem(mem)) th_explain(n_lits, lits, n_eqs, eqs, c, enode_pair(x, y)); + return new (sat::constraint_base::ptr2mem(mem)) th_explain(n_lits, lits, n_eqs, eqs, c, enode_pair(x, y), pma); } - th_explain* 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) { + th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, th_proof_hint const* pma) { return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), consequent, nullptr, nullptr, pma); } - th_explain* 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) { + th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma) { 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) { + th_explain* th_explain::propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma) { return mk(th, 0, nullptr, eqs.size(), eqs.data(), sat::null_literal, x, y, pma); } @@ -313,8 +305,8 @@ namespace euf { out << "--> " << m_consequent; if (m_eq.first != nullptr) out << "--> " << m_eq.first->get_expr_id() << " == " << m_eq.second->get_expr_id(); - if (m_pragma != nullptr) - out << " p " << m_pragma; + if (m_proof_hint != nullptr) + out << " p "; return out; } diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index 7209fa051..8289418fc 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -39,7 +39,7 @@ namespace euf { virtual bool post_visit(expr* e, bool sign, bool root) { return false; } public: - virtual ~th_internalizer() {} + virtual ~th_internalizer() = default; virtual sat::literal internalize(expr* e, bool sign, bool root, bool redundant) = 0; @@ -58,9 +58,10 @@ namespace euf { }; + class th_decompile { public: - virtual ~th_decompile() {} + virtual ~th_decompile() = default; virtual bool to_formulas(std::function& lit2expr, expr_ref_vector& fmls) { return false; } }; @@ -68,7 +69,7 @@ namespace euf { class th_model_builder { public: - virtual ~th_model_builder() {} + virtual ~th_model_builder() = default; /** \brief compute the value for enode \c n and store the value in \c values @@ -138,6 +139,11 @@ namespace euf { }; + class th_proof_hint : public sat::proof_hint { + public: + virtual expr* get_hint(euf::solver& s) const = 0; + }; + class th_euf_solver : public th_solver { protected: solver& ctx; @@ -150,16 +156,16 @@ namespace euf { region& get_region(); - sat::status mk_status(sat::proof_hint const* ps = nullptr); + sat::status mk_status(th_proof_hint const* ps = nullptr); bool add_unit(sat::literal lit); bool add_units(sat::literal_vector const& lits); bool add_clause(sat::literal lit) { return add_unit(lit); } bool add_clause(sat::literal a, sat::literal b); - bool add_clause(sat::literal a, sat::literal b, sat::proof_hint const* ps); + bool add_clause(sat::literal a, sat::literal b, th_proof_hint const* ps); bool add_clause(sat::literal a, sat::literal b, sat::literal c); bool add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d); - bool add_clause(sat::literal_vector const& lits, sat::proof_hint const* ps = nullptr) { return add_clause(lits.size(), lits.data(), ps); } - bool add_clause(unsigned n, sat::literal* lits, sat::proof_hint const* ps = nullptr); + bool add_clause(sat::literal_vector const& lits, th_proof_hint const* ps = nullptr) { return add_clause(lits.size(), lits.data(), ps); } + bool add_clause(unsigned n, sat::literal* lits, th_proof_hint const* ps = nullptr); void add_equiv(sat::literal a, sat::literal b); void add_equiv_and(sat::literal a, sat::literal_vector const& bs); @@ -220,16 +226,16 @@ namespace euf { * that retrieve literals on demand. */ class th_explain { - sat::literal m_consequent = sat::null_literal; // literal consequent for propagations - enode_pair m_eq = enode_pair(); // equality consequent for propagations + sat::literal m_consequent = sat::null_literal; // literal consequent for propagations + enode_pair m_eq = enode_pair(); // equality consequent for propagations + th_proof_hint const* m_proof_hint; unsigned m_num_literals; unsigned m_num_eqs; sat::literal* m_literals; enode_pair* m_eqs; - char* m_pragma = nullptr; - static size_t get_obj_size(unsigned num_lits, unsigned num_eqs, sat::proof_hint const* pma); - th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& eq, sat::proof_hint const* pma = nullptr); - static th_explain* mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, sat::proof_hint const* pma = nullptr); + static size_t get_obj_size(unsigned num_lits, unsigned num_eqs); + th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& eq, th_proof_hint const* pma = nullptr); + static th_explain* mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, th_proof_hint const* pma = nullptr); public: static th_explain* conflict(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs); @@ -240,9 +246,9 @@ 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); + static th_explain* propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_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, th_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, th_proof_hint const* pma = nullptr); sat::ext_constraint_idx to_index() const { return sat::constraint_base::mem2base(this); @@ -277,7 +283,7 @@ namespace euf { enode_pair eq_consequent() const { return m_eq; } - sat::proof_hint const* get_pragma() const { return nullptr; } //*m_pragma ? m_pragma : nullptr; } + th_proof_hint const* get_pragma() const { return m_proof_hint; } }; diff --git a/src/sat/smt/xor_solver.cpp b/src/sat/smt/xor_solver.cpp new file mode 100644 index 000000000..27354eac2 --- /dev/null +++ b/src/sat/smt/xor_solver.cpp @@ -0,0 +1,79 @@ +/*++ +Copyright (c) 2014 Microsoft Corporation + +Module Name: + + xor_solver.h + +Abstract: + + XOR solver. + Interface outline. + +--*/ + + +#include "sat/smt/xor_solver.h" +#include "sat/sat_simplifier_params.hpp" +#include "sat/sat_xor_finder.h" + +namespace xr { + + solver::solver(euf::solver& ctx): + th_solver(ctx.get_manager(), symbol("xor-solver"), ctx.get_manager().get_family_id("xor-solver")) + {} + + euf::th_solver* solver::clone(euf::solver& ctx) { + // and relevant copy internal state + return alloc(solver, ctx); + } + + void solver::asserted(sat::literal l) { + + } + + bool solver::unit_propagate() { + return false; + } + + void solver::get_antecedents(sat::literal l, sat::ext_justification_idx idx, + sat::literal_vector & r, bool probing) { + + } + + sat::check_result solver::check() { + return sat::check_result::CR_DONE; + } + + void solver::push() { + } + + void solver::pop(unsigned n) { + } + + // inprocessing + // pre_simplify: decompile all xor constraints to allow other in-processing. + // simplify: recompile clauses to xor constraints + // literals that get added to xor constraints are tagged with the theory. + void solver::pre_simplify() { + + } + + void solver::simplify() { + + } + + std::ostream& solver::display(std::ostream& out) const { + return out; + } + + std::ostream& solver::display_justification(std::ostream& out, sat::ext_justification_idx idx) const { + return out; + } + + std::ostream& solver::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const { + return out; + } + +} + diff --git a/src/sat/smt/xor_solver.h b/src/sat/smt/xor_solver.h new file mode 100644 index 000000000..3da30c580 --- /dev/null +++ b/src/sat/smt/xor_solver.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2014 Microsoft Corporation + +Module Name: + + xor_solver.h + +Abstract: + + XOR solver. + Interface outline. + +--*/ + +#pragma once + +#include "sat/smt/euf_solver.h" + +namespace xr { + class solver : public euf::th_solver { + public: + solver(euf::solver& ctx); + + th_solver* clone(euf::solver& ctx) override; + + sat::literal internalize(expr* e, bool sign, bool root, bool redundant) override { UNREACHABLE(); return sat::null_literal; } + + void internalize(expr* e, bool redundant) override { UNREACHABLE(); } + + + void asserted(sat::literal l) override; + bool unit_propagate() override; + void get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector & r, bool probing) override; + + void pre_simplify() override; + void simplify() override; + + sat::check_result check() override; + void push() override; + void pop(unsigned n) override; + + std::ostream& display(std::ostream& out) const override; + std::ostream& display_justification(std::ostream& out, sat::ext_justification_idx idx) const override; + std::ostream& display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const override; + + }; + +} diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index a5d9dec4e..2d390b4f2 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -75,7 +75,6 @@ struct goal2sat::imp : public sat::sat_internalizer { func_decl_ref_vector m_unhandled_funs; bool m_default_external; bool m_euf { false }; - bool m_drat { false }; bool m_is_redundant { false }; bool m_top_level { false }; sat::literal_vector aig_lits; @@ -102,7 +101,6 @@ struct goal2sat::imp : public sat::sat_internalizer { m_ite_extra = p.get_bool("ite_extra", true); m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); m_euf = sp.euf(); - m_drat = sp.drat_file().is_non_empty_string(); } void throw_op_not_handled(std::string const& s) { @@ -169,15 +167,9 @@ struct goal2sat::imp : public sat::sat_internalizer { if (m_expr2var_replay && m_expr2var_replay->find(n, v)) return v; v = m_solver.add_var(is_ext); - log_def(v, n); return v; } - void log_def(sat::bool_var v, expr* n) { - if (m_drat && m_euf) - ensure_euf()->drat_bool_def(v, n); - } - sat::bool_var to_bool_var(expr* e) override { sat::literal l; sat::bool_var v = m_map.to_bool_var(e); @@ -1013,6 +1005,11 @@ goal2sat::~goal2sat() { dealloc(m_imp); } +euf::solver* goal2sat::ensure_euf() { + return m_imp->ensure_euf(); +} + + void goal2sat::collect_param_descrs(param_descrs & r) { insert_max_memory(r); r.insert("ite_extra", CPK_BOOL, "(default: true) add redundant clauses (that improve unit propagation) when encoding if-then-else formulas"); diff --git a/src/sat/tactic/goal2sat.h b/src/sat/tactic/goal2sat.h index 08fd5f088..5f85d59ce 100644 --- a/src/sat/tactic/goal2sat.h +++ b/src/sat/tactic/goal2sat.h @@ -33,6 +33,10 @@ Notes: #include "sat/smt/atom2bool_var.h" #include "sat/smt/sat_internalizer.h" +namespace euf { + class solver; +} + class goal2sat { public: typedef obj_map dep2asm_map; @@ -41,7 +45,6 @@ private: imp * m_imp; unsigned m_scopes = 0; - void init(ast_manager& m, params_ref const & p, sat::solver_core & t, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external); public: goal2sat(); @@ -66,6 +69,9 @@ public: void operator()(ast_manager& m, unsigned n, expr* const* fmls, params_ref const & p, sat::solver_core & t, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external = false); + void init(ast_manager& m, params_ref const & p, sat::solver_core & t, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external); + + void assumptions(ast_manager& m, unsigned n, expr* const* fmls, params_ref const & p, sat::solver_core & t, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external = false); void get_interpreted_funs(func_decl_ref_vector& funs); @@ -82,4 +88,6 @@ public: void user_pop(unsigned n); + euf::solver* ensure_euf(); + }; diff --git a/src/shell/CMakeLists.txt b/src/shell/CMakeLists.txt index d55626d05..d9b74f162 100644 --- a/src/shell/CMakeLists.txt +++ b/src/shell/CMakeLists.txt @@ -34,6 +34,14 @@ add_executable(shell # we don't want (I think). ${shell_object_files} ) + +set_target_properties(shell PROPERTIES + # Position independent code needed in shared libraries + POSITION_INDEPENDENT_CODE ON + # Symbol visibility + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) + z3_add_install_tactic_rule(${shell_deps}) z3_add_memory_initializer_rule(${shell_deps}) z3_add_gparams_register_modules_rule(${shell_deps}) diff --git a/src/shell/drat_frontend.cpp b/src/shell/drat_frontend.cpp index 485b1af91..091d8cd4d 100644 --- a/src/shell/drat_frontend.cpp +++ b/src/shell/drat_frontend.cpp @@ -17,90 +17,22 @@ Copyright (c) 2020 Microsoft Corporation #include "cmd_context/cmd_context.h" #include "ast/proofs/proof_checker.h" #include "ast/rewriter/th_rewriter.h" +#include "ast/reg_decl_plugins.h" #include "sat/smt/arith_proof_checker.h" -class smt_checker { - ast_manager& m; +class drup_checker { sat::drat& m_drat; - expr_ref_vector const& m_b2e; - expr_ref_vector m_fresh_exprs; - expr_ref_vector m_core; - expr_ref_vector m_inputs; - params_ref m_params; - scoped_ptr m_lemma_solver, m_input_solver; sat::literal_vector m_units; - bool m_check_inputs { false }; - - expr* fresh(expr* e) { - unsigned i = e->get_id(); - m_fresh_exprs.reserve(i + 1); - expr* r = m_fresh_exprs.get(i); - if (!r) { - r = m.mk_fresh_const("sk", e->get_sort()); - m_fresh_exprs[i] = r; - } - return r; - } - - expr_ref define(expr* e, unsigned depth) { - expr_ref r(fresh(e), m); - m_core.push_back(m.mk_eq(r, e)); - if (depth == 0) - return r; - r = e; - if (is_app(e)) { - expr_ref_vector args(m); - for (expr* arg : *to_app(e)) - args.push_back(define(arg, depth - 1)); - r = m.mk_app(to_app(e)->get_decl(), args.size(), args.data()); - } - return r; - } - - void unfold1(sat::literal_vector const& lits) { - m_core.reset(); - for (sat::literal lit : lits) { - expr* e = m_b2e[lit.var()]; - expr_ref fml = define(e, 2); - if (!lit.sign()) - fml = m.mk_not(fml); - m_core.push_back(fml); - } - } - - expr_ref lit2expr(sat::literal lit) { - return expr_ref(lit.sign() ? m.mk_not(m_b2e[lit.var()]) : m_b2e[lit.var()], m); - } + bool m_check_inputs = false; void add_units() { auto const& units = m_drat.units(); -#if 0 - for (unsigned i = m_units.size(); i < units.size(); ++i) { - sat::literal lit = units[i].first; - m_lemma_solver->assert_expr(lit2expr(lit)); - } -#endif for (unsigned i = m_units.size(); i < units.size(); ++i) m_units.push_back(units[i].first); } void check_assertion_redundant(sat::literal_vector const& input) { - expr_ref_vector args(m); - for (auto lit : input) - args.push_back(lit2expr(lit)); - m_inputs.push_back(args.size() == 1 ? args.back() : m.mk_or(args)); - - m_input_solver->push(); - for (auto lit : input) { - m_input_solver->assert_expr(lit2expr(~lit)); - } - lbool is_sat = m_input_solver->check_sat(); - if (is_sat != l_false) { - std::cout << "Failed to verify input\n"; - exit(0); - } - m_input_solver->pop(1); } @@ -112,59 +44,71 @@ class smt_checker { */ sat::literal_vector drup_units; - void check_clause(sat::literal_vector const& lits) { - + void check_clause(sat::literal_vector const& lits) { + } + + void check_drup(sat::literal_vector const& lits) { add_units(); drup_units.reset(); if (m_drat.is_drup(lits.size(), lits.data(), drup_units)) { std::cout << "drup\n"; return; } - m_input_solver->push(); -// for (auto lit : drup_units) -// m_input_solver->assert_expr(lit2expr(lit)); - for (auto lit : lits) - m_input_solver->assert_expr(lit2expr(~lit)); - lbool is_sat = m_input_solver->check_sat(); - if (is_sat != l_false) { - std::cout << "did not verify: " << is_sat << " " << lits << "\n"; - for (sat::literal lit : lits) - std::cout << lit2expr(lit) << "\n"; - std::cout << "\n"; - m_input_solver->display(std::cout); - if (is_sat == l_true) { - model_ref mdl; - m_input_solver->get_model(mdl); - std::cout << *mdl << "\n"; - } - - exit(0); - } - m_input_solver->pop(1); - std::cout << "smt\n"; - // check_assertion_redundant(lits); + std::cout << "did not verify " << lits << "\n"; + exit(0); } public: - smt_checker(sat::drat& drat, expr_ref_vector const& b2e): - m(b2e.m()), m_drat(drat), m_b2e(b2e), m_fresh_exprs(m), m_core(m), m_inputs(m) { - m_lemma_solver = mk_smt_solver(m, m_params, symbol()); - m_input_solver = mk_smt_solver(m, m_params, symbol()); - } + drup_checker(sat::drat& drat): m_drat(drat) {} - void add(sat::literal_vector const& lits, sat::status const& st, bool validated) { + void add(sat::literal_vector const& lits, sat::status const& st) { for (sat::literal lit : lits) while (lit.var() >= m_drat.get_solver().num_vars()) m_drat.get_solver().mk_var(true); - if (st.is_input() && m_check_inputs) - check_assertion_redundant(lits); - else if (!st.is_sat() && !st.is_deleted() && !validated) - check_clause(lits); - // m_drat.add(lits, st); + if (st.is_sat()) + check_drup(lits); + m_drat.add(lits, st); } +}; + +unsigned read_drat(char const* drat_file) { + ast_manager m; + reg_decl_plugins(m); + std::ifstream ins(drat_file); + dimacs::drat_parser drat(ins, std::cerr); + + std::function read_theory = [&](char const* r) { + return m.mk_family_id(symbol(r)); + }; + std::function write_theory = [&](int th) { + return m.get_family_name(th); + }; + drat.set_read_theory(read_theory); + params_ref p; + reslimit lim; + sat::solver solver(p, lim); + sat::drat drat_checker(solver); + drup_checker checker(drat_checker); + + for (auto const& r : drat) { + std::cout << dimacs::drat_pp(r, write_theory); + std::cout.flush(); + checker.add(r.m_lits, r.m_status); + if (drat_checker.inconsistent()) { + std::cout << "inconsistent\n"; + return 0; + } + statistics st; + drat_checker.collect_statistics(st); + std::cout << st << "\n"; + } + return 0; +} + + +#if 0 bool validate_hint(expr_ref_vector const& exprs, sat::literal_vector const& lits, sat::proof_hint const& hint) { - // return; // remove when testing this arith_util autil(m); arith::proof_checker achecker(m); proof_checker pc(m); @@ -247,289 +191,16 @@ public: std::cout << "p hint not verified\n"; return false; } - std::cout << "p hint verified\n"; + return true; break; } default: UNREACHABLE(); break; } - return true; + return false; } - /** - * Add an assertion from the source file - */ - void add_assertion(expr* a) { - m_input_solver->assert_expr(a); - } - - void display_input() { - scoped_ptr s = mk_smt_solver(m, m_params, symbol()); - for (auto* e : m_inputs) - s->assert_expr(e); - s->display(std::cout); - } - - symbol name; - unsigned_vector params; - ptr_vector sorts; - - void parse_quantifier(sexpr_ref const& sexpr, cmd_context& ctx, quantifier_kind& k, sort_ref_vector& domain, svector& names) { - k = quantifier_kind::forall_k; - symbol q; - unsigned sz; - if (sexpr->get_kind() != sexpr::kind_t::COMPOSITE) - goto bail; - sz = sexpr->get_num_children(); - if (sz == 0) - goto bail; - q = sexpr->get_child(0)->get_symbol(); - if (q == "forall") - k = quantifier_kind::forall_k; - else if (q == "exists") - k = quantifier_kind::exists_k; - else if (q == "lambda") - k = quantifier_kind::lambda_k; - else - goto bail; - for (unsigned i = 1; i < sz; ++i) { - auto* e = sexpr->get_child(i); - if (e->get_kind() != sexpr::kind_t::COMPOSITE) - goto bail; - if (2 != e->get_num_children()) - goto bail; - symbol name = e->get_child(0)->get_symbol(); - std::ostringstream ostrm; - e->get_child(1)->display(ostrm); - std::istringstream istrm(ostrm.str()); - params_ref p; - auto srt = parse_smt2_sort(ctx, istrm, false, p, "quantifier"); - if (!srt) - goto bail; - names.push_back(name); - domain.push_back(srt); - } - return; - bail: - std::cout << "Could not parse expression\n"; - sexpr->display(std::cout); - std::cout << "\n"; - exit(0); - } - - void parse_sexpr(sexpr_ref const& sexpr, cmd_context& ctx, expr_ref_vector const& args, expr_ref& result) { - params.reset(); - sorts.reset(); - for (expr* arg : args) - sorts.push_back(arg->get_sort()); - sort_ref rng(m); - func_decl* f = nullptr; - switch (sexpr->get_kind()) { - case sexpr::kind_t::COMPOSITE: { - unsigned sz = sexpr->get_num_children(); - if (sz == 0) - goto bail; - if (sexpr->get_child(0)->get_symbol() == symbol("_")) { - name = sexpr->get_child(1)->get_symbol(); - if (name == "bv" && sz == 4) { - bv_util bvu(m); - auto val = sexpr->get_child(2)->get_numeral(); - auto n = sexpr->get_child(3)->get_numeral().get_unsigned(); - result = bvu.mk_numeral(val, n); - return; - } - if (name == "is" && sz == 3) { - name = sexpr->get_child(2)->get_child(0)->get_symbol(); - f = ctx.find_func_decl(name, params.size(), params.data(), args.size(), sorts.data(), rng.get()); - if (!f) - goto bail; - datatype_util dtu(m); - result = dtu.mk_is(f, args[0]); - return; - } - if (name == "Real" && sz == 4) { - arith_util au(m); - rational r = sexpr->get_child(2)->get_numeral(); - // rational den = sexpr->get_child(3)->get_numeral(); - result = au.mk_numeral(r, false); - return; - } - if (name == "Int" && sz == 4) { - arith_util au(m); - rational num = sexpr->get_child(2)->get_numeral(); - result = au.mk_numeral(num, true); - return; - } - for (unsigned i = 2; i < sz; ++i) { - auto* child = sexpr->get_child(i); - if (child->is_numeral() && child->get_numeral().is_unsigned()) - params.push_back(child->get_numeral().get_unsigned()); - else - goto bail; - } - break; - } - goto bail; - } - case sexpr::kind_t::SYMBOL: - name = sexpr->get_symbol(); - break; - case sexpr::kind_t::BV_NUMERAL: { - std::cout << "bv numeral\n"; - goto bail; - } - case sexpr::kind_t::STRING: - case sexpr::kind_t::KEYWORD: - case sexpr::kind_t::NUMERAL: - default: - goto bail; - } - f = ctx.find_func_decl(name, params.size(), params.data(), args.size(), sorts.data(), rng.get()); - if (!f) - goto bail; - result = ctx.m().mk_app(f, args); - return; - bail: - std::cout << "Could not parse expression\n"; - sexpr->display(std::cout); - std::cout << "\n"; - exit(0); - } -}; - -static void verify_smt(char const* drat_file, char const* smt_file) { - cmd_context ctx; - ctx.set_ignore_check(true); - ctx.set_regular_stream(std::cerr); - ctx.set_solver_factory(mk_smt_strategic_solver_factory()); - if (smt_file) { - std::ifstream smt_in(smt_file); - if (!parse_smt2_commands(ctx, smt_in)) { - std::cerr << "could not read file " << smt_file << "\n"; - return; - } - } - - std::ifstream ins(drat_file); - dimacs::drat_parser drat(ins, std::cerr); - ast_manager& m = ctx.m(); - std::function read_theory = [&](char const* r) { - return m.mk_family_id(symbol(r)); - }; - std::function write_theory = [&](int th) { - return m.get_family_name(th); - }; - drat.set_read_theory(read_theory); - params_ref p; - reslimit lim; - p.set_bool("drat.check_unsat", true); - sat::solver solver(p, lim); - sat::drat drat_checker(solver); - drat_checker.updt_config(); - - expr_ref_vector bool_var2expr(m); - expr_ref_vector exprs(m), args(m), inputs(m); - sort_ref_vector sargs(m), sorts(m); - func_decl_ref_vector decls(m); - - smt_checker checker(drat_checker, bool_var2expr); - - for (expr* a : ctx.assertions()) - checker.add_assertion(a); - - for (auto const& r : drat) { - std::cout << dimacs::drat_pp(r, write_theory); - std::cout.flush(); - switch (r.m_tag) { - case dimacs::drat_record::tag_t::is_clause: { - bool validated = checker.validate_hint(exprs, r.m_lits, r.m_hint); - checker.add(r.m_lits, r.m_status, validated); - if (drat_checker.inconsistent()) { - std::cout << "inconsistent\n"; - return; - } - break; - } - case dimacs::drat_record::tag_t::is_node: { - expr_ref e(m); - args.reset(); - for (auto n : r.m_args) - args.push_back(exprs.get(n)); - std::istringstream strm(r.m_name); - auto sexpr = parse_sexpr(ctx, strm, p, drat_file); - checker.parse_sexpr(sexpr, ctx, args, e); - exprs.reserve(r.m_node_id + 1); - exprs.set(r.m_node_id, e); - break; - } - case dimacs::drat_record::tag_t::is_var: { - var_ref e(m); - SASSERT(r.m_args.size() == 1); - std::istringstream strm(r.m_name); - auto srt = parse_smt2_sort(ctx, strm, false, p, drat_file); - e = m.mk_var(r.m_args[0], srt); - exprs.reserve(r.m_node_id + 1); - exprs.set(r.m_node_id, e); - break; - } - case dimacs::drat_record::tag_t::is_decl: { - std::istringstream strm(r.m_name); - ctx.set_allow_duplicate_declarations(); - parse_smt2_commands(ctx, strm); - break; - } - case dimacs::drat_record::tag_t::is_sort: { - sort_ref srt(m); - symbol name = symbol(r.m_name.c_str()); - sargs.reset(); - for (auto n : r.m_args) - sargs.push_back(sorts.get(n)); - psort_decl* pd = ctx.find_psort_decl(name); - if (pd) - srt = pd->instantiate(ctx.pm(), sargs.size(), sargs.data()); - else - srt = m.mk_uninterpreted_sort(name); - sorts.reserve(r.m_node_id + 1); - sorts.set(r.m_node_id, srt); - break; - } - case dimacs::drat_record::tag_t::is_quantifier: { - VERIFY(r.m_args.size() == 1); - quantifier_ref q(m); - std::istringstream strm(r.m_name); - auto sexpr = parse_sexpr(ctx, strm, p, drat_file); - sort_ref_vector domain(m); - svector names; - quantifier_kind k; - checker.parse_quantifier(sexpr, ctx, k, domain, names); - q = m.mk_quantifier(k, domain.size(), domain.data(), names.data(), exprs.get(r.m_args[0])); - exprs.reserve(r.m_node_id + 1); - exprs.set(r.m_node_id, q); - break; - } - case dimacs::drat_record::tag_t::is_bool_def: - bool_var2expr.reserve(r.m_node_id + 1); - bool_var2expr.set(r.m_node_id, exprs.get(r.m_args[0])); - break; - default: - UNREACHABLE(); - break; - } - } - statistics st; - drat_checker.collect_statistics(st); - std::cout << st << "\n"; -} - - -unsigned read_drat(char const* drat_file, char const* problem_file) { - if (!problem_file) { - std::cerr << "No smt2 file provided to checker\n"; - return -1; - } - verify_smt(drat_file, problem_file); - return 0; -} +#endif diff --git a/src/shell/drat_frontend.h b/src/shell/drat_frontend.h index ef37fcfaa..89760360e 100644 --- a/src/shell/drat_frontend.h +++ b/src/shell/drat_frontend.h @@ -3,6 +3,6 @@ Copyright (c) 2011 Microsoft Corporation --*/ #pragma once -unsigned read_drat(char const * drat_file, char const* problem_file); +unsigned read_drat(char const * drat_file); diff --git a/src/shell/main.cpp b/src/shell/main.cpp index 73eee2ad1..af3b22db0 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -95,6 +95,7 @@ void display_usage() { std::cout << " -p display Z3 global (and module) parameters.\n"; std::cout << " -pd display Z3 global (and module) parameter descriptions.\n"; std::cout << " -pm:name display Z3 module ('name') parameters.\n"; + std::cout << " -pmmd:name display Z3 module ('name') parameters in Markdown format.\n"; std::cout << " -pp:name display Z3 parameter description, if 'name' is not provided, then all module names are listed.\n"; std::cout << " -tactics[:name] display built-in tactics or if argument is given, display detailed information on tactic.\n"; std::cout << " -probes display avilable probes.\n"; @@ -246,6 +247,15 @@ static void parse_cmd_line_args(std::string& input_file, int argc, char ** argv) gparams::display(std::cout, 0, false, true); exit(0); } + else if (strcmp(opt_name, "pmmd") == 0) { + if (opt_arg) + gparams::display_module_markdown(std::cout, opt_arg); + else { + gparams::display_modules(std::cout); + std::cout << "\nUse -pm:name to display all parameters available at module 'name'\n"; + } + exit(0); + } else if (strcmp(opt_name, "pm") == 0) { if (opt_arg) { gparams::display_module(std::cout, opt_arg); @@ -392,7 +402,7 @@ int STD_CALL main(int argc, char ** argv) { return_value = read_mps_file(g_input_file); break; case IN_DRAT: - return_value = read_drat(g_drat_input_file, g_input_file); + return_value = read_drat(g_drat_input_file); break; default: UNREACHABLE(); diff --git a/src/shell/smtlib_frontend.cpp b/src/shell/smtlib_frontend.cpp index c86d4b5b9..d0d0b452d 100644 --- a/src/shell/smtlib_frontend.cpp +++ b/src/shell/smtlib_frontend.cpp @@ -26,6 +26,7 @@ Revision History: #include "parsers/smt2/smt2parser.h" #include "muz/fp/dl_cmds.h" #include "cmd_context/extra_cmds/dbg_cmds.h" +#include "cmd_context/extra_cmds/proof_cmds.h" #include "opt/opt_cmds.h" #include "cmd_context/extra_cmds/polynomial_cmds.h" #include "cmd_context/extra_cmds/subpaving_cmds.h" @@ -128,6 +129,7 @@ unsigned read_smtlib2_commands(char const * file_name) { install_subpaving_cmds(ctx); install_opt_cmds(ctx); install_smt2_extra_cmds(ctx); + install_proof_cmds(ctx); g_cmd_context = &ctx; signal(SIGINT, on_ctrl_c); diff --git a/src/smt/mam.h b/src/smt/mam.h index bf23b24a6..e7051b3f7 100644 --- a/src/smt/mam.h +++ b/src/smt/mam.h @@ -37,8 +37,7 @@ namespace smt { m_context(ctx) { } - virtual ~mam() { - } + virtual ~mam() = default; virtual void add_pattern(quantifier * q, app * mp) = 0; diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index ab5ee9a98..4e9afaa8d 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -19,6 +19,7 @@ Revision History: #include "smt/params/smt_params.h" #include "smt/params/smt_params_helper.hpp" #include "util/gparams.h" +#include "params/solver_params.hpp" void smt_params::updt_local_params(params_ref const & _p) { smt_params_helper p(_p); @@ -59,6 +60,10 @@ void smt_params::updt_local_params(params_ref const & _p) { m_dump_benchmarks = false; m_dump_min_time = 0.5; m_dump_recheck = false; + solver_params sp(_p); + m_axioms2files = sp.axioms2files(); + m_lemmas2console = sp.lemmas2console(); + m_instantiations2console = sp.instantiations2console(); } void smt_params::updt_params(params_ref const & p) { @@ -150,7 +155,8 @@ void smt_params::display(std::ostream & out) const { DISPLAY_PARAM(m_old_clause_relevancy); DISPLAY_PARAM(m_inv_clause_decay); - DISPLAY_PARAM(m_smtlib_dump_lemmas); + DISPLAY_PARAM(m_axioms2files); + DISPLAY_PARAM(m_lemmas2console); DISPLAY_PARAM(m_logic); DISPLAY_PARAM(m_string_solver); diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 5a13c69ac..62e1ec843 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -82,144 +82,146 @@ struct smt_params : public preprocessor_params, public theory_seq_params, public theory_pb_params, public theory_datatype_params { - bool m_display_proof; - bool m_display_dot_proof; - bool m_display_unsat_core; - bool m_check_proof; - bool m_eq_propagation; - bool m_binary_clause_opt; - unsigned m_relevancy_lvl; - bool m_relevancy_lemma; - unsigned m_random_seed; - double m_random_var_freq; - double m_inv_decay; - unsigned m_clause_decay; - initial_activity m_random_initial_activity; - phase_selection m_phase_selection; - unsigned m_phase_caching_on; - unsigned m_phase_caching_off; - bool m_minimize_lemmas; - unsigned m_max_conflicts; + bool m_display_proof = false; + bool m_display_dot_proof = false; + bool m_display_unsat_core = false; + bool m_check_proof = false; + bool m_eq_propagation = true; + bool m_binary_clause_opt = true; + unsigned m_relevancy_lvl = 2; + bool m_relevancy_lemma = false; + unsigned m_random_seed = 0; + double m_random_var_freq = 0.01; + double m_inv_decay = 1.052; + unsigned m_clause_decay = 1; + initial_activity m_random_initial_activity = initial_activity::IA_RANDOM_WHEN_SEARCHING; + phase_selection m_phase_selection = phase_selection::PS_CACHING_CONSERVATIVE; + unsigned m_phase_caching_on = 700; + unsigned m_phase_caching_off = 100; + bool m_minimize_lemmas = true; + unsigned m_max_conflicts = UINT_MAX; unsigned m_restart_max; - unsigned m_cube_depth; - unsigned m_threads; - unsigned m_threads_max_conflicts; - unsigned m_threads_cube_frequency; - bool m_simplify_clauses; - unsigned m_tick; - bool m_display_features; - bool m_new_core2th_eq; - bool m_ematching; - bool m_induction; - bool m_clause_proof; + unsigned m_cube_depth = 1; + unsigned m_threads = 1; + unsigned m_threads_max_conflicts = UINT_MAX; + unsigned m_threads_cube_frequency = 2; + bool m_simplify_clauses = true; + unsigned m_tick = 1000; + bool m_display_features = false; + bool m_new_core2th_eq = true; + bool m_ematching = true; + bool m_induction = false; + bool m_clause_proof = false; // ----------------------------------- // // Case split strategy // // ----------------------------------- - case_split_strategy m_case_split_strategy; - unsigned m_rel_case_split_order; - bool m_lookahead_diseq; - bool m_theory_case_split; - bool m_theory_aware_branching; + case_split_strategy m_case_split_strategy = case_split_strategy::CS_ACTIVITY_DELAY_NEW; + unsigned m_rel_case_split_order = 0; + bool m_lookahead_diseq = false; + bool m_theory_case_split = false; + bool m_theory_aware_branching = false; // ----------------------------------- // // Delay units... // // ----------------------------------- - bool m_delay_units; - unsigned m_delay_units_threshold; + bool m_delay_units = false; + unsigned m_delay_units_threshold = 32; // ----------------------------------- // // Conflict resolution // // ----------------------------------- - bool m_theory_resolve; + bool m_theory_resolve = false; // ----------------------------------- // // Restart // // ----------------------------------- - restart_strategy m_restart_strategy; - unsigned m_restart_initial; - double m_restart_factor; - bool m_restart_adaptive; - double m_agility_factor; - double m_restart_agility_threshold; + restart_strategy m_restart_strategy = restart_strategy::RS_IN_OUT_GEOMETRIC; + unsigned m_restart_initial = 100; + double m_restart_factor = 1.1; + bool m_restart_adaptive = true; + double m_agility_factor = 0.9999; + double m_restart_agility_threshold = 0.18; // ----------------------------------- // // Lemma garbage collection // // ----------------------------------- - lemma_gc_strategy m_lemma_gc_strategy; - bool m_lemma_gc_half; - unsigned m_recent_lemmas_size; - unsigned m_lemma_gc_initial; - double m_lemma_gc_factor; - unsigned m_new_old_ratio; //!< the ratio of new and old clauses. - unsigned m_new_clause_activity; - unsigned m_old_clause_activity; - unsigned m_new_clause_relevancy; //!< Max. number of unassigned literals to be considered relevant. - unsigned m_old_clause_relevancy; //!< Max. number of unassigned literals to be considered relevant. - double m_inv_clause_decay; //!< clause activity decay + lemma_gc_strategy m_lemma_gc_strategy = lemma_gc_strategy::LGC_FIXED; + bool m_lemma_gc_half = false; + unsigned m_recent_lemmas_size = 100; + unsigned m_lemma_gc_initial = 5000; + double m_lemma_gc_factor = 1.1; + unsigned m_new_old_ratio = 16; //!< the ratio of new and old clauses. + unsigned m_new_clause_activity = 10; + unsigned m_old_clause_activity = 500; + unsigned m_new_clause_relevancy = 45; //!< Max. number of unassigned literals to be considered relevant. + unsigned m_old_clause_relevancy = 6; //!< Max. number of unassigned literals to be considered relevant. + double m_inv_clause_decay = 1; //!< clause activity decay // ----------------------------------- // // SMT-LIB (debug) pretty printer // // ----------------------------------- - bool m_smtlib_dump_lemmas; - symbol m_logic; + bool m_axioms2files = false; + bool m_lemmas2console = false; + bool m_instantiations2console = false; + symbol m_logic = symbol::null; // ----------------------------------- // // Statistics for Profiling // // ----------------------------------- - bool m_profile_res_sub; - bool m_display_bool_var2expr; - bool m_display_ll_bool_var2expr; + bool m_profile_res_sub = false; + bool m_display_bool_var2expr = false; + bool m_display_ll_bool_var2expr = false; // ----------------------------------- // // Model generation // // ----------------------------------- - bool m_model; - bool m_model_on_timeout; - bool m_model_on_final_check; + bool m_model = true; + bool m_model_on_timeout = false; + bool m_model_on_final_check = false; // ----------------------------------- // // Progress sampling // // ----------------------------------- - unsigned m_progress_sampling_freq; + unsigned m_progress_sampling_freq = 0; // ----------------------------------- // // Debugging goodies // // ----------------------------------- - bool m_core_validate; + bool m_core_validate = false; // ----------------------------------- // // From front_end_params // // ----------------------------------- - bool m_preprocess; // temporary hack for disabling all preprocessing.. - bool m_user_theory_preprocess_axioms; - bool m_user_theory_persist_axioms; - bool m_at_labels_cex; // only use labels which contains the @ symbol when building multiple counterexamples. - bool m_check_at_labels; // check that @ labels are inserted to generate unique counter-examples. - bool m_dump_goal_as_smt; - bool m_auto_config; + bool m_preprocess = true; // temporary hack for disabling all preprocessing.. + bool m_user_theory_preprocess_axioms = false; + bool m_user_theory_persist_axioms = false; + bool m_at_labels_cex = false; // only use labels which contains the @ symbol when building multiple counterexamples. + bool m_check_at_labels = false; // check that @ labels are inserted to generate unique counter-examples. + bool m_dump_goal_as_smt = false; + bool m_auto_config = true; // ----------------------------------- // @@ -238,77 +240,6 @@ struct smt_params : public preprocessor_params, symbol m_string_solver; smt_params(params_ref const & p = params_ref()): - m_display_proof(false), - m_display_dot_proof(false), - m_display_unsat_core(false), - m_check_proof(false), - m_eq_propagation(true), - m_binary_clause_opt(true), - m_relevancy_lvl(2), - m_relevancy_lemma(false), - m_random_seed(0), - m_random_var_freq(0.01), - m_inv_decay(1.052), - m_clause_decay(1), - m_random_initial_activity(initial_activity::IA_RANDOM_WHEN_SEARCHING), - m_phase_selection(phase_selection::PS_CACHING_CONSERVATIVE), - m_phase_caching_on(700), - m_phase_caching_off(100), - m_minimize_lemmas(true), - m_max_conflicts(UINT_MAX), - m_cube_depth(1), - m_threads(1), - m_threads_max_conflicts(UINT_MAX), - m_threads_cube_frequency(2), - m_simplify_clauses(true), - m_tick(1000), - m_display_features(false), - m_new_core2th_eq(true), - m_ematching(true), - m_induction(false), - m_clause_proof(false), - m_case_split_strategy(case_split_strategy::CS_ACTIVITY_DELAY_NEW), - m_rel_case_split_order(0), - m_lookahead_diseq(false), - m_theory_case_split(false), - m_theory_aware_branching(false), - m_delay_units(false), - m_delay_units_threshold(32), - m_theory_resolve(false), - m_restart_strategy(restart_strategy::RS_IN_OUT_GEOMETRIC), - m_restart_initial(100), - m_restart_factor(1.1), - m_restart_adaptive(true), - m_agility_factor(0.9999), - m_restart_agility_threshold(0.18), - m_lemma_gc_strategy(lemma_gc_strategy::LGC_FIXED), - m_lemma_gc_half(false), - m_recent_lemmas_size(100), - m_lemma_gc_initial(5000), - m_lemma_gc_factor(1.1), - m_new_old_ratio(16), - m_new_clause_activity(10), - m_old_clause_activity(500), - m_new_clause_relevancy(45), - m_old_clause_relevancy(6), - m_inv_clause_decay(1), - m_smtlib_dump_lemmas(false), - m_logic(symbol::null), - m_profile_res_sub(false), - m_display_bool_var2expr(false), - m_display_ll_bool_var2expr(false), - m_model(true), - m_model_on_timeout(false), - m_model_on_final_check(false), - m_progress_sampling_freq(0), - m_core_validate(false), - m_preprocess(true), // temporary hack for disabling all preprocessing.. - m_user_theory_preprocess_axioms(false), - m_user_theory_persist_axioms(false), - m_at_labels_cex(false), - m_check_at_labels(false), - m_dump_goal_as_smt(false), - m_auto_config(true), m_string_solver(symbol("auto")){ updt_local_params(p); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 0ae520d8a..82722435b 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -48,9 +48,10 @@ def_module_params(module_name='smt', ('bv.reflect', BOOL, True, 'create enode for every bit-vector term'), ('bv.enable_int2bv', BOOL, True, 'enable support for int2bv and bv2int operators'), ('bv.watch_diseq', BOOL, False, 'use watch lists instead of eager axioms for bit-vectors'), - ('bv.delay', BOOL, True, 'delay internalize expensive bit-vector operations'), + ('bv.delay', BOOL, False, 'delay internalize expensive bit-vector operations'), ('bv.polysat', BOOL, False, 'use polysat bit-vector solver'), ('bv.eq_axioms', BOOL, True, 'enable redundant equality axioms for bit-vectors'), + ('bv.size_reduce', BOOL, False, 'turn assertions that set the upper bits of a bit-vector to constants into a substitution that replaces the bit-vector with constant bits. Useful for minimizing circuits as many input bits to circuits are constant'), ('arith.random_initial_value', BOOL, False, 'use random initial values in the simplex-based procedure for linear arithmetic'), ('arith.solver', UINT, 6, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'), ('arith.nl', BOOL, True, '(incomplete) nonlinear arithmetic support based on Groebner basis and interval propagation, relevant only if smt.arith.solver=2'), @@ -106,6 +107,8 @@ def_module_params(module_name='smt', ('core.validate', BOOL, False, '[internal] validate unsat core produced by SMT context. This option is intended for debugging'), ('seq.split_w_len', BOOL, True, 'enable splitting guided by length constraints'), ('seq.validate', BOOL, False, 'enable self-validation of theory axioms created by seq theory'), + ('seq.max_unfolding', UINT, 1000000000, 'maximal unfolding depth for checking string equations and regular expressions'), + ('seq.min_unfolding', UINT, 1, 'initial bound for strings whose lengths are bounded by iterative deepening. Set this to a higher value if there are only models with larger string lengths'), ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms'), ('str.aggressive_length_testing', BOOL, False, 'prioritize testing concrete length values over generating more options'), ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options'), diff --git a/src/smt/params/theory_arith_params.cpp b/src/smt/params/theory_arith_params.cpp index deec4e4da..565000ebe 100644 --- a/src/smt/params/theory_arith_params.cpp +++ b/src/smt/params/theory_arith_params.cpp @@ -34,7 +34,6 @@ void theory_arith_params::updt_params(params_ref const & _p) { m_arith_int_eq_branching = p.arith_int_eq_branch(); m_arith_ignore_int = p.arith_ignore_int(); m_arith_bound_prop = static_cast(p.arith_propagation_mode()); - m_arith_dump_lemmas = p.arith_dump_lemmas(); m_arith_eager_eq_axioms = p.arith_eager_eq_axioms(); m_arith_auto_config_simplex = p.arith_auto_config_simplex(); @@ -67,7 +66,6 @@ void theory_arith_params::display(std::ostream & out) const { DISPLAY_PARAM(m_arith_adaptive); DISPLAY_PARAM(m_arith_adaptive_assertion_threshold); DISPLAY_PARAM(m_arith_adaptive_propagation_threshold); - DISPLAY_PARAM(m_arith_dump_lemmas); DISPLAY_PARAM(m_arith_eager_eq_axioms); DISPLAY_PARAM(m_arith_branch_cut_ratio); DISPLAY_PARAM(m_arith_int_eq_branching); diff --git a/src/smt/params/theory_arith_params.h b/src/smt/params/theory_arith_params.h index d45a902db..526cb6f09 100644 --- a/src/smt/params/theory_arith_params.h +++ b/src/smt/params/theory_arith_params.h @@ -72,7 +72,6 @@ struct theory_arith_params { bool m_arith_adaptive = false; double m_arith_adaptive_assertion_threshold = 0.2; double m_arith_adaptive_propagation_threshold = 0.4; - bool m_arith_dump_lemmas = false; bool m_arith_eager_eq_axioms = true; unsigned m_arith_branch_cut_ratio = 2; bool m_arith_int_eq_branching = false; diff --git a/src/smt/params/theory_bv_params.cpp b/src/smt/params/theory_bv_params.cpp index 38a6e5fb0..572878cf8 100644 --- a/src/smt/params/theory_bv_params.cpp +++ b/src/smt/params/theory_bv_params.cpp @@ -29,6 +29,7 @@ void theory_bv_params::updt_params(params_ref const & _p) { m_bv_delay = p.bv_delay(); m_bv_polysat = p.bv_polysat(); m_bv_eq_axioms = p.bv_eq_axioms(); + m_bv_size_reduce = p.bv_size_reduce(); } #define DISPLAY_PARAM(X) out << #X"=" << X << std::endl; @@ -44,4 +45,5 @@ void theory_bv_params::display(std::ostream & out) const { DISPLAY_PARAM(m_bv_enable_int2bv2int); DISPLAY_PARAM(m_bv_delay); DISPLAY_PARAM(m_bv_polysat); + DISPLAY_PARAM(m_bv_size_reduce); } diff --git a/src/smt/params/theory_bv_params.h b/src/smt/params/theory_bv_params.h index 5cfa2fe9a..53573ba69 100644 --- a/src/smt/params/theory_bv_params.h +++ b/src/smt/params/theory_bv_params.h @@ -37,6 +37,7 @@ struct theory_bv_params { bool m_bv_watch_diseq = false; bool m_bv_delay = true; bool m_bv_polysat = false; + bool m_bv_size_reduce = false; theory_bv_params(params_ref const & p = params_ref()) { updt_params(p); } diff --git a/src/smt/params/theory_seq_params.cpp b/src/smt/params/theory_seq_params.cpp index 68cce6e66..290b2631d 100644 --- a/src/smt/params/theory_seq_params.cpp +++ b/src/smt/params/theory_seq_params.cpp @@ -21,4 +21,6 @@ void theory_seq_params::updt_params(params_ref const & _p) { smt_params_helper p(_p); m_split_w_len = p.seq_split_w_len(); m_seq_validate = p.seq_validate(); + m_seq_max_unfolding = p.seq_max_unfolding(); + m_seq_min_unfolding = p.seq_min_unfolding(); } diff --git a/src/smt/params/theory_seq_params.h b/src/smt/params/theory_seq_params.h index 84437fa15..f964088eb 100644 --- a/src/smt/params/theory_seq_params.h +++ b/src/smt/params/theory_seq_params.h @@ -24,6 +24,8 @@ struct theory_seq_params { */ bool m_split_w_len = false; bool m_seq_validate = false; + unsigned m_seq_max_unfolding = UINT_MAX/4; + unsigned m_seq_min_unfolding = 1; theory_seq_params(params_ref const & p = params_ref()) { updt_params(p); diff --git a/src/smt/qi_queue.cpp b/src/smt/qi_queue.cpp index de52cdb01..f8fbe739e 100644 --- a/src/smt/qi_queue.cpp +++ b/src/smt/qi_queue.cpp @@ -23,6 +23,7 @@ Revision History: #include "ast/rewriter/var_subst.h" #include "smt/smt_context.h" #include "smt/qi_queue.h" +#include namespace smt { @@ -228,15 +229,12 @@ namespace smt { TRACE("qi_queue", tout << "new instance:\n" << mk_pp(instance, m) << "\n";); - TRACE("qi_queue_instance", tout << "new instance:\n" << mk_pp(instance, m) << "\n";); expr_ref s_instance(m); proof_ref pr(m); m_context.get_rewriter()(instance, s_instance, pr); TRACE("qi_queue_bug", tout << "new instance after simplification:\n" << s_instance << "\n";); if (m.is_true(s_instance)) { - TRACE("checker", tout << "reduced to true, before:\n" << mk_ll_pp(instance, m);); - STRACE("instance", tout << "Instance reduced to true\n";); stat -> inc_num_instances_simplify_true(); if (m.has_trace_stream()) { @@ -250,9 +248,11 @@ namespace smt { std::cout << "instantiate\n"; enode_vector _bindings(num_bindings, bindings); for (auto * b : _bindings) - std::cout << enode_pp(b, m_context) << " "; + std::cout << mk_pp(b->get_expr(), m) << " "; std::cout << "\n"; std::cout << mk_pp(q, m) << "\n"; + std::cout << "instance\n"; + std::cout << instance << "\n"; #endif TRACE("qi_queue", tout << "simplified instance:\n" << s_instance << "\n";); diff --git a/src/smt/seq_axioms.cpp b/src/smt/seq_axioms.cpp index fee223612..bd4d8dec2 100644 --- a/src/smt/seq_axioms.cpp +++ b/src/smt/seq_axioms.cpp @@ -86,7 +86,7 @@ void seq_axioms::add_clause(expr_ref_vector const& clause) { justification* js = ctx().mk_justification( ext_theory_eq_propagation_justification( - th.get_id(), ctx().get_region(), 0, nullptr, 0, nullptr, n1, n2)); + th.get_id(), ctx(), 0, nullptr, 0, nullptr, n1, n2)); ctx().assign_eq(n1, n2, eq_justification(js)); return; } diff --git a/src/smt/smt_case_split_queue.h b/src/smt/smt_case_split_queue.h index 91f7fc036..a84b5ea4f 100644 --- a/src/smt/smt_case_split_queue.h +++ b/src/smt/smt_case_split_queue.h @@ -45,7 +45,7 @@ namespace smt { virtual void pop_scope(unsigned num_scopes) = 0; virtual void next_case_split(bool_var & next, lbool & phase) = 0; virtual void display(std::ostream & out) = 0; - virtual ~case_split_queue() {} + virtual ~case_split_queue() = default; // theory-aware branching hint virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) {} diff --git a/src/smt/smt_clause.h b/src/smt/smt_clause.h index 950d0e17c..89d8f2d66 100644 --- a/src/smt/smt_clause.h +++ b/src/smt/smt_clause.h @@ -33,7 +33,7 @@ namespace smt { */ class clause_del_eh { public: - virtual ~clause_del_eh() {} + virtual ~clause_del_eh() = default; virtual void operator()(ast_manager & m, clause * cls) = 0; }; diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 420147256..1bae0ef86 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -80,7 +80,8 @@ namespace smt { m_unsat_core(m), m_mk_bool_var_trail(*this), m_mk_enode_trail(*this), - m_mk_lambda_trail(*this) { + m_mk_lambda_trail(*this), + m_lemma_visitor(m) { SASSERT(m_scope_lvl == 0); SASSERT(m_base_lvl == 0); @@ -2560,7 +2561,7 @@ namespace smt { justification * js = cls.get_justification(); justification * new_js = nullptr; if (js->in_region()) - new_js = mk_justification(unit_resolution_justification(m_region, + new_js = mk_justification(unit_resolution_justification(*this, js, simp_lits.size(), simp_lits.data())); @@ -2618,7 +2619,7 @@ namespace smt { if (!cls_js || cls_js->in_region()) { // If cls_js is 0 or is allocated in a region, then // we can allocate the new justification in a region too. - js = mk_justification(unit_resolution_justification(m_region, + js = mk_justification(unit_resolution_justification(*this, cls_js, simp_lits.size(), simp_lits.data())); @@ -3197,13 +3198,22 @@ namespace smt { void context::internalize_assertions() { if (get_cancel_flag()) return; + if (m_internalizing_assertions) return; + flet _internalizing(m_internalizing_assertions, true); TRACE("internalize_assertions", tout << "internalize_assertions()...\n";); timeit tt(get_verbosity_level() >= 100, "smt.preprocessing"); - reduce_assertions(); - if (get_cancel_flag()) return; - if (!m_asserted_formulas.inconsistent()) { - unsigned sz = m_asserted_formulas.get_num_formulas(); - unsigned qhead = m_asserted_formulas.get_qhead(); + unsigned qhead = 0; + do { + reduce_assertions(); + if (get_cancel_flag()) + return; + if (m_asserted_formulas.inconsistent()) { + if (!inconsistent()) + asserted_inconsistent(); + break; + } + qhead = m_asserted_formulas.get_qhead(); + unsigned sz = m_asserted_formulas.get_num_formulas(); while (qhead < sz) { if (get_cancel_flag()) { m_asserted_formulas.commit(qhead); @@ -3213,13 +3223,12 @@ namespace smt { proof * pr = m_asserted_formulas.get_formula_proof(qhead); SASSERT(!pr || f == m.get_fact(pr)); internalize_assertion(f, pr, 0); - qhead++; + ++qhead; } m_asserted_formulas.commit(); } - if (m_asserted_formulas.inconsistent() && !inconsistent()) { - asserted_inconsistent(); - } + while (qhead < m_asserted_formulas.get_num_formulas()); + TRACE("internalize_assertions", tout << "after internalize_assertions()...\n"; tout << "inconsistent: " << inconsistent() << "\n";); TRACE("after_internalize_assertions", display(tout);); diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 8d98fc60e..7ca2e1c63 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -107,11 +107,12 @@ namespace smt { ptr_vector m_justifications; - unsigned m_final_check_idx; // circular counter used for implementing fairness + unsigned m_final_check_idx = 0; // circular counter used for implementing fairness - bool m_is_auxiliary { false }; // used to prevent unwanted information from being logged. - class parallel* m_par { nullptr }; - unsigned m_par_index { 0 }; + bool m_is_auxiliary = false; // used to prevent unwanted information from being logged. + class parallel* m_par = nullptr; + unsigned m_par_index = 0; + bool m_internalizing_assertions = false; // ----------------------------------- // @@ -777,8 +778,6 @@ namespace smt { bool has_lambda(); - bool is_beta_redex(enode* p, enode* n) const; - void internalize_lambda(quantifier * q); void internalize_formula_core(app * n, bool gate_ctx); @@ -892,6 +891,10 @@ namespace smt { void remove_lit_occs(clause const& cls, unsigned num_bool_vars); void add_lit_occs(clause const& cls); + + ast_pp_util m_lemma_visitor; + void dump_lemma(unsigned n, literal const* lits); + void dump_axiom(unsigned n, literal const* lits); public: void ensure_internalized(expr* e); @@ -1031,6 +1034,8 @@ namespace smt { bool is_shared(enode * n) const; + bool is_beta_redex(enode* p, enode* n) const; + void assign_eq(enode * lhs, enode * rhs, eq_justification const & js) { push_eq(lhs, rhs, js); } diff --git a/src/smt/smt_context_pp.cpp b/src/smt/smt_context_pp.cpp index 77b42c6c2..24bfb3355 100644 --- a/src/smt/smt_context_pp.cpp +++ b/src/smt/smt_context_pp.cpp @@ -766,11 +766,11 @@ namespace smt { for (; p2 + 2 < str.size(); ++p2) l2 << " "; l1 << ")\n"; l2 << ")\n"; - IF_VERBOSE(1, verbose_stream() << l1.str() << l2.str()); + IF_VERBOSE(2, verbose_stream() << l1.str() << l2.str()); m_last_positions.reset(); m_last_positions.append(offsets); } - IF_VERBOSE(1, verbose_stream() << str); + IF_VERBOSE(2, verbose_stream() << str); } }; diff --git a/src/smt/smt_for_each_relevant_expr.h b/src/smt/smt_for_each_relevant_expr.h index 4b4492b7f..20be165c6 100644 --- a/src/smt/smt_for_each_relevant_expr.h +++ b/src/smt/smt_for_each_relevant_expr.h @@ -65,7 +65,7 @@ namespace smt { public: for_each_relevant_expr(context & ctx); - virtual ~for_each_relevant_expr() {} + virtual ~for_each_relevant_expr() = default; /** \brief Visit the relevant sub-expressions of n. That is, only subexpressions m of n, such that m_context.is_relevant(m). diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 092bc0127..d87a4f971 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -24,6 +24,8 @@ Revision History: #include "smt/smt_model_finder.h" #include "ast/for_each_expr.h" +#include + namespace smt { /** @@ -613,11 +615,15 @@ namespace smt { bool context::has_lambda() { for (auto const & [n, q] : m_lambdas) { - if (n->get_class_size() != 1) + if (n->get_class_size() != 1) { + TRACE("context", tout << "class size " << n->get_class_size() << " " << enode_pp(n, *this) << "\n"); return true; + } for (enode* p : enode::parents(n)) - if (!is_beta_redex(p, n)) + if (!is_beta_redex(p, n)) { + TRACE("context", tout << "not a beta redex " << enode_pp(p, *this) << "\n"); return true; + } } return false; } @@ -1372,8 +1378,10 @@ namespace smt { TRACE("mk_clause", display_literals_verbose(tout << "creating clause: " << literal_vector(num_lits, lits) << "\n", num_lits, lits) << "\n";); m_clause_proof.add(num_lits, lits, k, j); switch (k) { - case CLS_AUX: - case CLS_TH_AXIOM: { + case CLS_TH_AXIOM: + dump_axiom(num_lits, lits); + Z3_fallthrough; + case CLS_AUX: { literal_buffer simp_lits; if (!simplify_aux_clause_literals(num_lits, lits, simp_lits)) { if (j && !j->in_region()) { @@ -1384,11 +1392,12 @@ namespace smt { } DEBUG_CODE(for (literal lit : simp_lits) SASSERT(get_assignment(lit) == l_true);); if (!simp_lits.empty()) { - j = mk_justification(unit_resolution_justification(m_region, j, simp_lits.size(), simp_lits.data())); + j = mk_justification(unit_resolution_justification(*this, j, simp_lits.size(), simp_lits.data())); } break; } - case CLS_TH_LEMMA: + case CLS_TH_LEMMA: + dump_lemma(num_lits, lits); if (!simplify_aux_lemma_literals(num_lits, lits)) { if (j && !j->in_region()) { j->del_eh(m); @@ -1398,7 +1407,10 @@ namespace smt { } // simplify_aux_lemma_literals does not delete literals assigned to false, so // it is not necessary to create a unit_resolution_justification - break; + break; + case CLS_LEARNED: + dump_lemma(num_lits, lits); + break; default: break; } @@ -1503,6 +1515,30 @@ namespace smt { }} } + void context::dump_axiom(unsigned n, literal const* lits) { + if (m_fparams.m_axioms2files) { + literal_buffer tmp; + neg_literals(n, lits, tmp); + SASSERT(tmp.size() == n); + display_lemma_as_smt_problem(tmp.size(), tmp.data(), false_literal, m_fparams.m_logic); + } + } + + void context::dump_lemma(unsigned n, literal const* lits) { + + if (m_fparams.m_lemmas2console) { + expr_ref fml(m); + expr_ref_vector fmls(m); + for (unsigned i = 0; i < n; ++i) + fmls.push_back(literal2expr(lits[i])); + fml = mk_or(fmls); + m_lemma_visitor.collect(fml); + m_lemma_visitor.display_skolem_decls(std::cout); + m_lemma_visitor.display_assert(std::cout, fml.get(), true); + } + + } + void context::mk_clause(literal l1, literal l2, justification * j) { literal ls[2] = { l1, l2 }; mk_clause(2, ls, j); @@ -1518,13 +1554,7 @@ namespace smt { TRACE("mk_th_axiom", display_literals_verbose(tout, num_lits, lits) << "\n";); if (m.proofs_enabled()) { - js = mk_justification(theory_axiom_justification(tid, m_region, num_lits, lits, num_params, params)); - } - if (m_fparams.m_smtlib_dump_lemmas) { - literal_buffer tmp; - neg_literals(num_lits, lits, tmp); - SASSERT(tmp.size() == num_lits); - display_lemma_as_smt_problem(tmp.size(), tmp.data(), false_literal, m_fparams.m_logic); + js = mk_justification(theory_axiom_justification(tid, *this, num_lits, lits, num_params, params)); } mk_clause(num_lits, lits, js, k); } diff --git a/src/smt/smt_justification.cpp b/src/smt/smt_justification.cpp index acdf03222..0124a1810 100644 --- a/src/smt/smt_justification.cpp +++ b/src/smt/smt_justification.cpp @@ -40,13 +40,14 @@ namespace smt { return m_proof; } - unit_resolution_justification::unit_resolution_justification(region & r, + unit_resolution_justification::unit_resolution_justification(context& ctx, justification * js, unsigned num_lits, literal const * lits): m_antecedent(js), m_num_literals(num_lits) { SASSERT(!js || js->in_region()); + auto& r = ctx.get_region(); m_literals = new (r) literal[num_lits]; memcpy(m_literals, lits, sizeof(literal) * num_lits); TRACE("unit_resolution_justification_bug", tout << literal_vector(num_lits, lits) << "\n";); @@ -101,6 +102,7 @@ namespace smt { return m.mk_unit_resolution(prs.size(), prs.data()); } + void eq_conflict_justification::get_antecedents(conflict_resolution & cr) { SASSERT(m_node1->get_root()->is_interpreted()); SASSERT(m_node2->get_root()->is_interpreted()); @@ -235,8 +237,9 @@ namespace smt { return nullptr; } - simple_justification::simple_justification(region & r, unsigned num_lits, literal const * lits): + simple_justification::simple_justification(context& ctx, unsigned num_lits, literal const * lits): m_num_literals(num_lits) { + region& r = ctx.get_region(); if (num_lits != 0) { m_literals = new (r) literal[num_lits]; memcpy(m_literals, lits, sizeof(literal) * num_lits); @@ -291,6 +294,12 @@ namespace smt { return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.data(), m_params.size(), m_params.data()); } + void theory_propagation_justification::log(context& ctx) { + if (ctx.get_fparams().m_axioms2files) + ctx.display_lemma_as_smt_problem(m_num_literals, m_literals, m_consequent); + } + + proof * theory_conflict_justification::mk_proof(conflict_resolution & cr) { ptr_buffer prs; if (!antecedent2proof(cr, prs)) @@ -299,9 +308,16 @@ namespace smt { return m.mk_th_lemma(m_th_id, m.mk_false(), prs.size(), prs.data(), m_params.size(), m_params.data()); } - ext_simple_justification::ext_simple_justification(region & r, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs): - simple_justification(r, num_lits, lits), + void theory_conflict_justification::log(context& ctx) { + if (ctx.get_fparams().m_axioms2files) + ctx.display_lemma_as_smt_problem(m_num_literals, m_literals); + } + + + ext_simple_justification::ext_simple_justification(context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs): + simple_justification(ctx, num_lits, lits), m_num_eqs(num_eqs) { + region& r = ctx.get_region(); m_eqs = new (r) enode_pair[num_eqs]; std::uninitialized_copy(eqs, eqs + num_eqs, m_eqs); DEBUG_CODE({ @@ -342,6 +358,13 @@ namespace smt { ctx.literal2expr(m_consequent, fact); return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.data(), m_params.size(), m_params.data()); } + + void ext_theory_propagation_justification::log(context& ctx) { + if (ctx.get_fparams().m_axioms2files) + ctx.display_lemma_as_smt_problem(m_num_literals, m_literals, m_num_eqs, m_eqs, m_consequent); + + } + proof * ext_theory_conflict_justification::mk_proof(conflict_resolution & cr) { ptr_buffer prs; @@ -351,6 +374,12 @@ namespace smt { return m.mk_th_lemma(m_th_id, m.mk_false(), prs.size(), prs.data(), m_params.size(), m_params.data()); } + void ext_theory_conflict_justification::log(context& ctx) { + if (ctx.get_fparams().m_axioms2files) + ctx.display_lemma_as_smt_problem(m_num_literals, m_literals); + } + + proof * ext_theory_eq_propagation_justification::mk_proof(conflict_resolution & cr) { ptr_buffer prs; if (!antecedent2proof(cr, prs)) @@ -361,6 +390,9 @@ namespace smt { return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.data(), m_params.size(), m_params.data()); } + void ext_theory_eq_propagation_justification::log(context& ctx) { + } + theory_lemma_justification::theory_lemma_justification(family_id fid, context & ctx, unsigned num_lits, literal const * lits, unsigned num_params, parameter* params): diff --git a/src/smt/smt_justification.h b/src/smt/smt_justification.h index b90233792..161ffe839 100644 --- a/src/smt/smt_justification.h +++ b/src/smt/smt_justification.h @@ -49,7 +49,7 @@ namespace smt { unsigned m_in_region:1; // true if the object was allocated in a region. public: justification(bool in_region = true):m_mark(false), m_in_region(in_region) {} - virtual ~justification() {} + virtual ~justification() = default; /** \brief This method should return true if the method del_eh needs to be invoked @@ -95,6 +95,7 @@ namespace smt { virtual char const * get_name() const { return "unknown"; } virtual void display_debug_info(conflict_resolution & cr, std::ostream & out) { /* do nothing */ } + }; class justification_proof_wrapper : public justification { @@ -118,7 +119,7 @@ namespace smt { unsigned m_num_literals; literal * m_literals; public: - unit_resolution_justification(region & r, justification * js, unsigned num_lits, literal const * lits); + unit_resolution_justification(context& ctx, justification * js, unsigned num_lits, literal const * lits); unit_resolution_justification(justification * js, unsigned num_lits, literal const * lits); @@ -137,6 +138,7 @@ namespace smt { proof * mk_proof(conflict_resolution & cr) override; char const * get_name() const override { return "unit-resolution"; } + }; class eq_conflict_justification : public justification { @@ -218,7 +220,7 @@ namespace smt { bool antecedent2proof(conflict_resolution & cr, ptr_buffer & result); public: - simple_justification(region & r, unsigned num_lits, literal const * lits); + simple_justification(context& ctx, unsigned num_lits, literal const * lits); void get_antecedents(conflict_resolution & cr) override; @@ -234,10 +236,10 @@ namespace smt { vector m_params; public: simple_theory_justification( - family_id fid, region & r, + family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_params, parameter* params): - simple_justification(r, num_lits, lits), + simple_justification(ctx, num_lits, lits), m_th_id(fid), m_params(num_params, params) {} bool has_del_eh() const override { return !m_params.empty(); } @@ -251,10 +253,10 @@ namespace smt { class theory_axiom_justification : public simple_theory_justification { public: - theory_axiom_justification(family_id fid, region & r, + theory_axiom_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_params = 0, parameter* params = nullptr): - simple_theory_justification(fid, r, num_lits, lits, num_params, params) {} + simple_theory_justification(fid, ctx, num_lits, lits, num_params, params) {} void get_antecedents(conflict_resolution & cr) override {} @@ -265,10 +267,11 @@ namespace smt { class theory_propagation_justification : public simple_theory_justification { literal m_consequent; + void log(context& ctx); public: - theory_propagation_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, literal consequent, + theory_propagation_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, literal consequent, unsigned num_params = 0, parameter* params = nullptr): - simple_theory_justification(fid, r, num_lits, lits, num_params, params), m_consequent(consequent) {} + simple_theory_justification(fid, ctx, num_lits, lits, num_params, params), m_consequent(consequent) { log(ctx); } proof * mk_proof(conflict_resolution & cr) override; @@ -278,10 +281,11 @@ namespace smt { }; class theory_conflict_justification : public simple_theory_justification { + void log(context& ctx); public: - theory_conflict_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + theory_conflict_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_params = 0, parameter* params = nullptr): - simple_theory_justification(fid, r, num_lits, lits, num_params, params) {} + simple_theory_justification(fid, ctx, num_lits, lits, num_params, params) { log(ctx); } proof * mk_proof(conflict_resolution & cr) override; @@ -299,7 +303,7 @@ namespace smt { bool antecedent2proof(conflict_resolution & cr, ptr_buffer & result); public: - ext_simple_justification(region & r, unsigned num_lits, literal const * lits, + ext_simple_justification(context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs); void get_antecedents(conflict_resolution & cr) override; @@ -318,10 +322,10 @@ namespace smt { vector m_params; public: - ext_theory_simple_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + ext_theory_simple_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs, unsigned num_params = 0, parameter* params = nullptr): - ext_simple_justification(r, num_lits, lits, num_eqs, eqs), m_th_id(fid), m_params(num_params, params) {} + ext_simple_justification(ctx, num_lits, lits, num_eqs, eqs), m_th_id(fid), m_params(num_params, params) {} bool has_del_eh() const override { return !m_params.empty(); } @@ -332,26 +336,33 @@ namespace smt { class ext_theory_propagation_justification : public ext_theory_simple_justification { literal m_consequent; + void log(context& ctx); public: - ext_theory_propagation_justification(family_id fid, region & r, + ext_theory_propagation_justification(family_id fid, context & ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs, literal consequent, unsigned num_params = 0, parameter* params = nullptr): - ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params), - m_consequent(consequent) {} + ext_theory_simple_justification(fid, ctx, num_lits, lits, num_eqs, eqs, num_params, params), + m_consequent(consequent) { + log(ctx); + } proof * mk_proof(conflict_resolution & cr) override; char const * get_name() const override { return "ext-theory-propagation"; } + }; class ext_theory_conflict_justification : public ext_theory_simple_justification { + void log(context& ctx); public: - ext_theory_conflict_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + ext_theory_conflict_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs, unsigned num_params = 0, parameter* params = nullptr): - ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params) {} + ext_theory_simple_justification(fid, ctx, num_lits, lits, num_eqs, eqs, num_params, params) { + log(ctx); + } proof * mk_proof(conflict_resolution & cr) override; @@ -361,19 +372,20 @@ namespace smt { class ext_theory_eq_propagation_justification : public ext_theory_simple_justification { enode * m_lhs; enode * m_rhs; + void log(context& ctx); public: ext_theory_eq_propagation_justification( - family_id fid, region & r, + family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs, enode * lhs, enode * rhs, unsigned num_params = 0, parameter* params = nullptr): - ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params), m_lhs(lhs), m_rhs(rhs) {} + ext_theory_simple_justification(fid, ctx, num_lits, lits, num_eqs, eqs, num_params, params), m_lhs(lhs), m_rhs(rhs) { log(ctx); } ext_theory_eq_propagation_justification( - family_id fid, region & r, + family_id fid, context& ctx, enode * lhs, enode * rhs): - ext_theory_simple_justification(fid, r, 0, nullptr, 0, nullptr, 0, nullptr), m_lhs(lhs), m_rhs(rhs) {} + ext_theory_simple_justification(fid, ctx, 0, nullptr, 0, nullptr, 0, nullptr), m_lhs(lhs), m_rhs(rhs) { log(ctx); } proof * mk_proof(conflict_resolution & cr) override; diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 18a290d0a..72094f78a 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -218,7 +218,7 @@ namespace smt { TRACE("model_checker", tout << "Got some value " << sk_value << "\n";); if (use_inv) { - unsigned sk_term_gen = 0; + unsigned sk_term_gen = 0; expr * sk_term = m_model_finder.get_inv(q, i, sk_value, sk_term_gen); if (sk_term != nullptr) { TRACE("model_checker", tout << "Found inverse " << mk_pp(sk_term, m) << "\n";); @@ -233,21 +233,30 @@ namespace smt { } else { expr * sk_term = get_term_from_ctx(sk_value); + func_decl * f = nullptr; if (sk_term != nullptr) { + TRACE("model_checker", tout << "sk term " << mk_pp(sk_term, m) << "\n"); sk_value = sk_term; } + // last ditch: am I an array? + else if (false && autil.is_as_array(sk_value, f) && cex->get_func_interp(f) && cex->get_func_interp(f)->get_array_interp(f)) { + sk_value = cex->get_func_interp(f)->get_array_interp(f); + } + } if (contains_model_value(sk_value)) { + TRACE("model_checker", tout << "type compatible term " << mk_pp(sk_value, m) << "\n"); sk_value = get_type_compatible_term(sk_value); } func_decl * f = nullptr; if (autil.is_as_array(sk_value, f) && cex->get_func_interp(f) && cex->get_func_interp(f)->get_interp()) { expr_ref body(cex->get_func_interp(f)->get_interp(), m); + if (contains_model_value(body)) + return false; ptr_vector sorts(f->get_arity(), f->get_domain()); svector names; - for (unsigned i = 0; i < f->get_arity(); ++i) { + for (unsigned i = 0; i < f->get_arity(); ++i) names.push_back(symbol(i)); - } defined_names dn(m); body = replace_value_from_ctx(body); body = m.mk_lambda(sorts.size(), sorts.data(), names.data(), body); @@ -395,12 +404,14 @@ namespace smt { m_fparams = alloc(smt_params, m_context->get_fparams()); m_fparams->m_relevancy_lvl = 0; // no relevancy since the model checking problems are quantifier free m_fparams->m_case_split_strategy = CS_ACTIVITY; // avoid warning messages about smt.case_split >= 3. - m_fparams->m_arith_dump_lemmas = false; + m_fparams->m_axioms2files = false; + m_fparams->m_lemmas2console = false; } if (!m_aux_context) { symbol logic; params_ref p; - p.set_bool("arith.dump_lemmas", false); + p.set_bool("solver.axioms2files", false); + p.set_bool("solver.lemmas2console", false); m_aux_context = m_context->mk_fresh(&logic, m_fparams.get(), p); } } @@ -563,16 +574,16 @@ namespace smt { for (unsigned i = 0; i < n; ++i) { proof* pr = nullptr; expr* arg = args[i]; - if (m.proofs_enabled()) { + if (m.proofs_enabled()) pr = m.mk_def_intro(arg); - } m_context->internalize_assertion(arg, pr, gen); } } TRACE("model_checker_bug_detail", tout << "instantiating... q:\n" << mk_pp(q, m) << "\n"; tout << "inconsistent: " << m_context->inconsistent() << "\n"; - tout << "bindings:\n" << expr_ref_vector(m, num_decls, m_pinned_exprs.data() + offset) << "\n";); + tout << "bindings:\n" << expr_ref_vector(m, num_decls, m_pinned_exprs.data() + offset) << "\n"; + tout << "def " << mk_pp(inst.m_def, m) << "\n";); m_context->add_instance(q, nullptr, num_decls, bindings.data(), inst.m_def, gen, gen, gen, dummy); TRACE("model_checker_bug_detail", tout << "after instantiating, inconsistent: " << m_context->inconsistent() << "\n";); } diff --git a/src/smt/smt_model_finder.cpp b/src/smt/smt_model_finder.cpp index 24ec4d5b9..94a0992f3 100644 --- a/src/smt/smt_model_finder.cpp +++ b/src/smt/smt_model_finder.cpp @@ -1157,7 +1157,7 @@ namespace smt { ast_manager& m; public: qinfo(ast_manager& m) :m(m) {} - virtual ~qinfo() {} + virtual ~qinfo() = default; virtual char const* get_kind() const = 0; virtual bool is_equal(qinfo const* qi) const = 0; virtual void display(std::ostream& out) const { out << "[" << get_kind() << "]"; } diff --git a/src/smt/smt_model_generator.h b/src/smt/smt_model_generator.h index 80bd59799..632557e98 100644 --- a/src/smt/smt_model_generator.h +++ b/src/smt/smt_model_generator.h @@ -136,7 +136,7 @@ namespace smt { */ class model_value_proc { public: - virtual ~model_value_proc() {} + virtual ~model_value_proc() = default; /** \brief Fill result with the dependencies of this functor. That is, to invoke mk_value, the dependencies in result must be constructed. diff --git a/src/smt/smt_quantifier.h b/src/smt/smt_quantifier.h index d9a43ddea..abb3cac7c 100644 --- a/src/smt/smt_quantifier.h +++ b/src/smt/smt_quantifier.h @@ -106,7 +106,7 @@ namespace smt { class quantifier_manager_plugin { public: - virtual ~quantifier_manager_plugin() {} + virtual ~quantifier_manager_plugin() = default; virtual void set_manager(quantifier_manager & qm) = 0; diff --git a/src/smt/smt_relevancy.h b/src/smt/smt_relevancy.h index 87cc3847c..f64b7d059 100644 --- a/src/smt/smt_relevancy.h +++ b/src/smt/smt_relevancy.h @@ -29,7 +29,7 @@ namespace smt { void mark_as_relevant(relevancy_propagator & rp, expr * n); void mark_args_as_relevant(relevancy_propagator & rp, app * n); public: - virtual ~relevancy_eh() {} + virtual ~relevancy_eh() = default; /** \brief This method is invoked when n is marked as relevant. */ @@ -87,7 +87,7 @@ namespace smt { context & m_context; public: relevancy_propagator(context & ctx); - virtual ~relevancy_propagator() {} + virtual ~relevancy_propagator() = default; context & get_context() { return m_context; } diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index af7b97c72..07709666b 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -294,7 +294,7 @@ namespace smt { m_bound_kind(k), m_atom(a) { } - virtual ~bound() {} + virtual ~bound() = default; theory_var get_var() const { return m_var; } bound_kind get_bound_kind() const { return static_cast(m_bound_kind); } bool is_atom() const { return m_atom; } @@ -544,9 +544,6 @@ namespace smt { unsigned small_lemma_size() const { return m_params.m_arith_small_lemma_size; } bool relax_bounds() const { return m_params.m_arith_stronger_lemmas; } bool skip_big_coeffs() const { return m_params.m_arith_skip_rows_with_big_coeffs; } - bool dump_lemmas() const { return m_params.m_arith_dump_lemmas; } - void dump_lemmas(literal l, antecedents const& ante); - void dump_lemmas(literal l, derived_bound const& ante); bool process_atoms() const; unsigned get_num_conflicts() const { return m_num_conflicts; } var_kind get_var_kind(theory_var v) const { return m_data[v].kind(); } diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 012ad695b..2d18a3496 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -563,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), qr(m); + expr_ref eqz(m), eq(m), lower(m), upper(m), qr(m), qr1(m), le(m), ge(m); div = m_util.mk_idiv(dividend, divisor); mod = m_util.mk_mod(dividend, divisor); zero = m_util.mk_int(0); @@ -584,9 +584,24 @@ namespace smt { mk_axiom(eqz, eq, false); mk_axiom(eqz, lower, false); mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); + rational k; - m_arith_eq_adapter.mk_axioms(ensure_enode(qr), ensure_enode(mod)); + m_arith_eq_adapter.mk_axioms(ensure_enode(qr), ensure_enode(dividend)); + + // non-linear divisors/mod have to be flattened for the non-linear solver to understand the terms. + // to ensure this use the rewriter. This is a hack required to fix a latent bug that affects the + // legacy arithmetic solver broadly. It is not something that the newer arithmetic solver suffers from. + qr1 = qr; + s(qr1); + if (qr != qr1) { + expr_ref eq(m.mk_eq(qr, qr1), m); + ctx.internalize(eq, false); + literal qeq = ctx.get_literal(eq); + ctx.mark_as_relevant(qeq); + ctx.mk_th_axiom(get_id(), 1, &qeq); + m_arith_eq_adapter.mk_axioms(ensure_enode(qr), ensure_enode(qr1)); + } if (m_util.is_zero(dividend)) { mk_axiom(eqz, m.mk_eq(div, zero)); @@ -1029,13 +1044,14 @@ namespace smt { inf_numeral const & k1(a1->get_k()); atom_kind kind1 = a1->get_atom_kind(); TRACE("mk_bound_axioms", display_atom(tout << "making bound axioms for " << a1 << " ", a1, true); tout << "\n";); + typename atoms::iterator it = occs.begin(); typename atoms::iterator end = occs.end(); - typename atoms::iterator lo_inf = end, lo_sup = end; typename atoms::iterator hi_inf = end, hi_sup = end; + for (; it != end; ++it) { - atom * a2 = *it; + atom* a2 = *it; inf_numeral const & k2(a2->get_k()); atom_kind kind2 = a2->get_atom_kind(); TRACE("mk_bound_axioms", display_atom(tout << "compare " << a2 << " ", a2, true); tout << "\n";); @@ -2978,23 +2994,7 @@ namespace smt { } } - template - void theory_arith::dump_lemmas(literal l, antecedents const& ante) { - if (dump_lemmas()) { - TRACE("arith", ante.display(tout) << " --> "; ctx.display_detailed_literal(tout, l); tout << "\n";); - ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().data(), - ante.eqs().size(), ante.eqs().data(), l); - } - } - - template - void theory_arith::dump_lemmas(literal l, derived_bound const& ante) { - if (dump_lemmas()) { - ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().data(), - ante.eqs().size(), ante.eqs().data(), l); - } - } template void theory_arith::assign_bound_literal(literal l, row const & r, unsigned idx, bool is_lower, inf_numeral & delta) { @@ -3006,28 +3006,23 @@ namespace smt { ante.display(tout) << " --> "; ctx.display_detailed_literal(tout, l); tout << "\n";); - dump_lemmas(l, ante); if (ante.lits().size() < small_lemma_size() && ante.eqs().empty()) { literal_vector & lits = m_tmp_literal_vector2; lits.reset(); lits.push_back(l); - literal_vector::const_iterator it = ante.lits().begin(); - literal_vector::const_iterator end = ante.lits().end(); - for (; it != end; ++it) - lits.push_back(~(*it)); + for (auto const& lit : ante.lits()) + lits.push_back(~lit); justification * js = nullptr; - if (proofs_enabled()) { + if (proofs_enabled()) js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.data(), ante.num_params(), ante.params("assign-bounds")); - } ctx.mk_clause(lits.size(), lits.data(), js, CLS_TH_LEMMA, nullptr); } else { - region & r = ctx.get_region(); ctx.assign(l, ctx.mk_justification( ext_theory_propagation_justification( - get_id(), r, ante.lits().size(), ante.lits().data(), + get_id(), ctx, ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), l, ante.num_params(), ante.params("assign-bounds")))); } @@ -3048,19 +3043,15 @@ namespace smt { int upper_idx; is_row_useful_for_bound_prop(r, lower_idx, upper_idx); - if (lower_idx >= 0) { + if (lower_idx >= 0) imply_bound_for_monomial(r, lower_idx, true); - } - else if (lower_idx == -1) { + else if (lower_idx == -1) imply_bound_for_all_monomials(r, true); - } - if (upper_idx >= 0) { + if (upper_idx >= 0) imply_bound_for_monomial(r, upper_idx, false); - } - else if (upper_idx == -1) { + else if (upper_idx == -1) imply_bound_for_all_monomials(r, false); - } // sneaking cheap eq detection in this loop propagate_cheap_eq(r_idx); @@ -3090,13 +3081,11 @@ namespace smt { template void theory_arith::set_conflict(antecedents const& ante, antecedents& bounds, char const* proof_rule) { set_conflict(ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), bounds, proof_rule); - dump_lemmas(false_literal, ante); } template void theory_arith::set_conflict(derived_bound const& ante, antecedents& bounds, char const* proof_rule) { set_conflict(ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), bounds, proof_rule); - dump_lemmas(false_literal, ante); } template @@ -3130,7 +3119,7 @@ namespace smt { record_conflict(num_literals, lits, num_eqs, eqs, bounds.num_params(), bounds.params(proof_rule)); ctx.set_conflict( ctx.mk_justification( - ext_theory_conflict_justification(get_id(), ctx.get_region(), num_literals, lits, num_eqs, eqs, + ext_theory_conflict_justification(get_id(), ctx, num_literals, lits, num_eqs, eqs, bounds.num_params(), bounds.params(proof_rule)))); } diff --git a/src/smt/theory_arith_eq.h b/src/smt/theory_arith_eq.h index 0b8e41e20..cffac2459 100644 --- a/src/smt/theory_arith_eq.h +++ b/src/smt/theory_arith_eq.h @@ -37,7 +37,7 @@ namespace smt { return; SASSERT(is_fixed(v)); - // WARNINING: it is not safe to use get_value(v) here, since + // WARNING: it is not safe to use get_value(v) here, since // get_value(v) may not satisfy v bounds at this point. if (!lower_bound(v).is_rational()) return; @@ -328,7 +328,6 @@ namespace smt { return; } context & ctx = get_context(); - region & r = ctx.get_region(); enode * _x = get_enode(x); enode * _y = get_enode(y); eq_vector const& eqs = antecedents.eqs(); @@ -336,7 +335,7 @@ namespace smt { justification * js = ctx.mk_justification( ext_theory_eq_propagation_justification( - get_id(), r, + get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), _x, _y, diff --git a/src/smt/theory_arith_int.h b/src/smt/theory_arith_int.h index f6a713d0d..699ebd5d2 100644 --- a/src/smt/theory_arith_int.h +++ b/src/smt/theory_arith_int.h @@ -487,13 +487,13 @@ namespace smt { template class theory_arith::gomory_cut_justification : public ext_theory_propagation_justification { public: - gomory_cut_justification(family_id fid, region & r, + gomory_cut_justification(family_id fid, context& ctx, unsigned num_lits, literal const * lits, unsigned num_eqs, enode_pair const * eqs, antecedents& bounds, literal consequent): - ext_theory_propagation_justification(fid, r, num_lits, lits, num_eqs, eqs, consequent, - bounds.num_params(), bounds.params("gomory-cut")) { + ext_theory_propagation_justification(fid, ctx, num_lits, lits, num_eqs, eqs, consequent, + bounds.num_params(), bounds.params("gomory-cut")) { } // Remark: the assignment must be propagated back to arith theory_id get_from_theory() const override { return null_theory_id; } @@ -676,19 +676,16 @@ namespace smt { l = ctx.get_literal(bound); IF_VERBOSE(10, verbose_stream() << "cut " << bound << "\n"); ctx.mark_as_relevant(l); - dump_lemmas(l, ante); auto js = ctx.mk_justification( gomory_cut_justification( - get_id(), ctx.get_region(), + get_id(), ctx, ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), ante, l)); - if (l == false_literal) { + if (l == false_literal) ctx.mk_clause(0, nullptr, js, CLS_TH_LEMMA, nullptr); - } - else { + else ctx.assign(l, js); - } return true; } @@ -714,7 +711,7 @@ namespace smt { for (; it != end; ++it) { if (!it->is_dead()) { if (is_fixed(it->m_var)) { - // WARNINING: it is not safe to use get_value(it->m_var) here, since + // WARNING: it is not safe to use get_value(it->m_var) here, since // get_value(it->m_var) may not satisfy it->m_var bounds at this point. numeral aux = lcm_den * it->m_coeff; consts += aux * lower_bound(it->m_var).get_rational(); @@ -760,7 +757,7 @@ namespace smt { ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), ante.lits().size(), ante.lits().data(), + get_id(), ctx, ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), ante.num_params(), ante.params("gcd-test")))); return false; @@ -840,7 +837,7 @@ namespace smt { ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), + get_id(), ctx, ante.lits().size(), ante.lits().data(), ante.eqs().size(), ante.eqs().data(), ante.num_params(), ante.params("gcd-test")))); return false; @@ -858,11 +855,9 @@ namespace smt { return true; if (m_eager_gcd) return true; - typename vector::const_iterator it = m_rows.begin(); - typename vector::const_iterator end = m_rows.end(); - for (; it != end; ++it) { - theory_var v = it->get_base_var(); - if (v != null_theory_var && is_int(v) && !get_value(v).is_int() && !gcd_test(*it)) { + for (auto const& e : m_rows) { + theory_var v = e.get_base_var(); + if (v != null_theory_var && is_int(v) && !get_value(v).is_int() && !gcd_test(e)) { if (m_params.m_arith_adaptive_gcd) m_eager_gcd = true; return false; @@ -883,10 +878,8 @@ namespace smt { for (;;) { vars.reset(); // Collect infeasible integer variables. - typename vector::const_iterator it = m_rows.begin(); - typename vector::const_iterator end = m_rows.end(); - for (; it != end; ++it) { - theory_var v = it->get_base_var(); + for (auto const& e : m_rows) { + theory_var v = e.get_base_var(); if (v != null_theory_var && is_int(v) && !get_value(v).is_int() && !is_bounded(v) && !already_processed.contains(v)) { vars.push_back(v); already_processed.insert(v); @@ -1056,15 +1049,13 @@ namespace smt { TRACE("arith_int_rows", unsigned num = 0; - typename vector::const_iterator it = m_rows.begin(); - typename vector::const_iterator end = m_rows.end(); - for (; it != end; ++it) { - theory_var v = it->get_base_var(); + for (auto const& e : m_rows) { + theory_var v = e.get_base_var(); if (v == null_theory_var) continue; if (is_int(v) && !get_value(v).is_int()) { num++; - display_simplified_row(tout, *it); + display_simplified_row(tout, e); tout << "\n"; } } diff --git a/src/smt/theory_array_base.cpp b/src/smt/theory_array_base.cpp index 8d545d3b4..a05fbc68d 100644 --- a/src/smt/theory_array_base.cpp +++ b/src/smt/theory_array_base.cpp @@ -54,6 +54,20 @@ namespace smt { return r; } + app * theory_array_base::mk_select_reduce(unsigned num_args, expr * * args) { + array_util util(m); + while (util.is_store(args[0])) { + bool are_distinct = false; + for (unsigned i = 1; i < num_args && !are_distinct; ++i) + are_distinct |= m.are_distinct(args[i], to_app(args[0])->get_arg(i)); + if (!are_distinct) + break; + args[0] = to_app(to_app(args[0])->get_arg(0)); + } + return mk_select(num_args, args); + } + + app * theory_array_base::mk_store(unsigned num_args, expr * const * args) { return m.mk_app(get_family_id(), OP_STORE, 0, nullptr, num_args, args); } @@ -484,6 +498,8 @@ namespace smt { return p->get_arg(0)->get_root() == n->get_root(); if (is_map(p)) return true; + if (is_store(p)) + return true; return false; } diff --git a/src/smt/theory_array_base.h b/src/smt/theory_array_base.h index f0d698f26..58d143ff1 100644 --- a/src/smt/theory_array_base.h +++ b/src/smt/theory_array_base.h @@ -62,6 +62,7 @@ namespace smt { bool is_select_arg(enode* r); app * mk_select(unsigned num_args, expr * const * args); + app * mk_select_reduce(unsigned num_args, expr * * args); app * mk_select(expr_ref_vector const& args) { return mk_select(args.size(), args.data()); } app * mk_store(unsigned num_args, expr * const * args); app * mk_default(expr* a); diff --git a/src/smt/theory_array_full.cpp b/src/smt/theory_array_full.cpp index 4311ef32c..120e12418 100644 --- a/src/smt/theory_array_full.cpp +++ b/src/smt/theory_array_full.cpp @@ -330,7 +330,8 @@ namespace smt { // Even if there was, as-array on interpreted // functions will be incomplete. // The instantiation operations are still sound to include. - found_unsupported_op(n); + m_as_array.push_back(node); + ctx.push_trail(push_back_vector(m_as_array)); instantiate_default_as_array_axiom(node); } else if (is_array_ext(n)) { @@ -604,6 +605,14 @@ namespace smt { 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); + ctx.get_rewriter()(val); + if (has_quantifiers(val)) { + expr_ref fn(m.mk_fresh_const("lambda-body", val->get_sort()), m); + expr_ref eq(m.mk_eq(fn, val), m); + ctx.assert_expr(eq); + ctx.internalize_assertions(); + val = fn; + } ctx.internalize(def, false); ctx.internalize(val.get(), false); return try_assign_eq(val.get(), def); @@ -807,12 +816,24 @@ namespace smt { if (r == FC_DONE && m_bapa) { r = m_bapa->final_check(); } - bool should_giveup = m_found_unsupported_op || has_propagate_up_trail(); + bool should_giveup = m_found_unsupported_op || has_propagate_up_trail() || has_non_beta_as_array(); if (r == FC_DONE && should_giveup) r = FC_GIVEUP; return r; } + bool theory_array_full::has_non_beta_as_array() { + for (enode* n : m_as_array) { + for (enode* p : n->get_parents()) + if (!ctx.is_beta_redex(p, n)) { + TRACE("array", tout << "not a beta redex " << enode_pp(p, ctx) << "\n"); + return true; + } + } + return false; + } + + bool theory_array_full::instantiate_parent_stores_default(theory_var v) { SASSERT(v != null_theory_var); TRACE("array", tout << "v" << v << "\n";); diff --git a/src/smt/theory_array_full.h b/src/smt/theory_array_full.h index 09a6daaec..210426e10 100644 --- a/src/smt/theory_array_full.h +++ b/src/smt/theory_array_full.h @@ -85,6 +85,8 @@ namespace smt { bool has_large_domain(app* array_term); bool has_unitary_domain(app* array_term); std::pair mk_epsilon(sort* s); + enode_vector m_as_array; + bool has_non_beta_as_array(); bool instantiate_select_const_axiom(enode* select, enode* cnst); bool instantiate_select_as_array_axiom(enode* select, enode* arr); diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index 196f49281..a01fdd483 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -294,8 +294,7 @@ namespace smt { m_trail_stack.push(add_var_pos_trail(b)); b->m_occs = new (get_region()) var_pos_occ(v, idx, b->m_occs); } - else { - SASSERT(th_id == null_theory_id); + else if (th_id == null_theory_id) { ctx.set_var_theory(l.var(), get_id()); SASSERT(ctx.get_var_theory(l.var()) == get_id()); bit_atom * b = new (get_region()) bit_atom(); @@ -600,6 +599,8 @@ namespace smt { TRACE("bv", tout << mk_bounded_pp(n, m) << "\n";); process_args(n); mk_enode(n); + m_bv2int.push_back(ctx.get_enode(n)); + ctx.push_trail(push_back_vector(m_bv2int)); if (!ctx.relevancy()) assert_bv2int_axiom(n); } @@ -1497,24 +1498,34 @@ namespace smt { unsigned sz = m_bits[v1].size(); bool changed = true; TRACE("bv", tout << "bits size: " << sz << "\n";); - if (sz == 0) { + if (sz == 0 && !m_bv2int.empty()) { // 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); + auto propagate_bv2int = [&](enode* bv2int) { + enode* bv2int_arg = get_arg(bv2int, 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, get_arg(p, 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)); + ext_theory_eq_propagation_justification(get_id(), ctx, 0, nullptr, eqs.size(), eqs.data(), p, bv2int_arg)); ctx.assign_eq(p, bv2int_arg, eq_justification(js)); break; } } + }; + + if (m_bv2int.size() < n1->get_class_size()) { + for (enode* bv2int : m_bv2int) + if (bv2int->get_root() == n1->get_root()) + propagate_bv2int(bv2int); + } + else { + for (enode* bv2int : *n1) { + if (m_util.is_bv2int(bv2int->get_expr())) + propagate_bv2int(bv2int); + } } } do { diff --git a/src/smt/theory_bv.h b/src/smt/theory_bv.h index e26db51d0..a66aaab8a 100644 --- a/src/smt/theory_bv.h +++ b/src/smt/theory_bv.h @@ -42,7 +42,7 @@ namespace smt { class atom { public: - virtual ~atom() {} + virtual ~atom() = default; virtual bool is_bit() const = 0; }; @@ -112,6 +112,7 @@ namespace smt { svector m_wpos; // per var, watch position for fixed variable detection. vector m_zero_one_bits; // per var, see comment in the struct zero_one_bit bool_var2atom m_bool_var2atom; + enode_vector m_bv2int; typedef svector vars; typedef std::pair value_sort_pair; diff --git a/src/smt/theory_char.cpp b/src/smt/theory_char.cpp index 772438986..dc41dfec5 100644 --- a/src/smt/theory_char.cpp +++ b/src/smt/theory_char.cpp @@ -138,7 +138,7 @@ namespace smt { enode* n2 = ensure_enode(bits2char); justification* j = ctx.mk_justification( - ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), n1, n2)); + ext_theory_eq_propagation_justification(get_id(), ctx, n1, n2)); ctx.assign_eq(n1, n2, eq_justification(j)); } ++m_stats.m_num_blast; @@ -267,7 +267,7 @@ namespace smt { enode* n2 = ensure_enode(sum_bits); justification* j = ctx.mk_justification( - ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), n1, n2)); + ext_theory_eq_propagation_justification(get_id(), ctx, n1, n2)); ctx.assign_eq(n1, n2, eq_justification(j)); } diff --git a/src/smt/theory_datatype.cpp b/src/smt/theory_datatype.cpp index febd2763c..d1216c171 100644 --- a/src/smt/theory_datatype.cpp +++ b/src/smt/theory_datatype.cpp @@ -31,8 +31,8 @@ namespace smt { class dt_eq_justification : public ext_theory_eq_propagation_justification { public: - dt_eq_justification(family_id fid, region & r, literal antecedent, enode * lhs, enode * rhs): - ext_theory_eq_propagation_justification(fid, r, 1, &antecedent, 0, nullptr, lhs, rhs) { + dt_eq_justification(family_id fid, context& ctx, literal antecedent, enode * lhs, enode * rhs): + ext_theory_eq_propagation_justification(fid, ctx, 1, &antecedent, 0, nullptr, lhs, rhs) { } // Remark: the assignment must be propagated back to the datatype theory. theory_id get_from_theory() const override { return null_theory_id; } @@ -122,9 +122,8 @@ namespace smt { } else { SASSERT(ctx.get_assignment(antecedent) == l_true); - region & r = ctx.get_region(); enode * _rhs = ctx.get_enode(rhs); - justification * js = ctx.mk_justification(dt_eq_justification(get_id(), r, antecedent, lhs, _rhs)); + justification * js = ctx.mk_justification(dt_eq_justification(get_id(), ctx, antecedent, lhs, _rhs)); TRACE("datatype", tout << "assigning... #" << lhs->get_owner_id() << " #" << _rhs->get_owner_id() << "\n"; tout << "v" << lhs->get_th_var(get_id()) << " v" << _rhs->get_th_var(get_id()) << "\n";); TRACE("datatype_detail", display(tout);); @@ -209,9 +208,8 @@ namespace smt { l.neg(); SASSERT(ctx.get_assignment(l) == l_true); enode_pair p(c, r->get_arg(0)); - region & reg = ctx.get_region(); clear_mark(); - ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), reg, 1, &l, 1, &p))); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), ctx, 1, &l, 1, &p))); } /** @@ -732,9 +730,8 @@ namespace smt { if (res) { // m_used_eqs should contain conflict - region & r = ctx.get_region(); clear_mark(); - ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), r, 0, nullptr, m_used_eqs.size(), m_used_eqs.data()))); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), ctx, 0, nullptr, m_used_eqs.size(), m_used_eqs.data()))); } return res; } @@ -859,10 +856,9 @@ namespace smt { var_data * d2 = m_var_data[v2]; if (d2->m_constructor != nullptr) { if (d1->m_constructor != nullptr && d1->m_constructor->get_decl() != d2->m_constructor->get_decl()) { - region & r = ctx.get_region(); enode_pair p(d1->m_constructor, d2->m_constructor); SASSERT(d1->m_constructor->get_root() == d2->m_constructor->get_root()); - ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), r, 0, nullptr, 1, &p))); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), ctx, 0, nullptr, 1, &p))); } if (d1->m_constructor == nullptr) { m_trail_stack.push(set_ptr_trail(d1->m_constructor)); @@ -972,14 +968,13 @@ namespace smt { if (num_unassigned == 0) { // conflict SASSERT(!lits.empty()); - region & reg = ctx.get_region(); TRACE("datatype_conflict", tout << mk_ismt2_pp(recognizer->get_expr(), m) << "\n"; for (literal l : lits) ctx.display_detailed_literal(tout, l) << "\n"; 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()))); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data()))); } else if (num_unassigned == 1) { // propagate remaining recognizer @@ -997,9 +992,8 @@ namespace smt { consequent = literal(ctx.enode2bool_var(r)); } ctx.mark_as_relevant(consequent); - region & reg = ctx.get_region(); ctx.assign(consequent, - ctx.mk_justification(ext_theory_propagation_justification(get_id(), reg, lits.size(), lits.data(), + ctx.mk_justification(ext_theory_propagation_justification(get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), consequent))); } else { diff --git a/src/smt/theory_dense_diff_logic.h b/src/smt/theory_dense_diff_logic.h index 8ab091a27..0a2987f8d 100644 --- a/src/smt/theory_dense_diff_logic.h +++ b/src/smt/theory_dense_diff_logic.h @@ -238,7 +238,6 @@ namespace smt { void flush_eh() override; void reset_eh() override; - bool dump_lemmas() const { return m_params.m_arith_dump_lemmas; } void display(std::ostream & out) const override; virtual void display_atom(std::ostream & out, atom * a) const; diff --git a/src/smt/theory_dense_diff_logic_def.h b/src/smt/theory_dense_diff_logic_def.h index b5abc7a2a..8f69c9489 100644 --- a/src/smt/theory_dense_diff_logic_def.h +++ b/src/smt/theory_dense_diff_logic_def.h @@ -539,7 +539,7 @@ namespace smt { literal_vector & antecedents = m_tmp_literals; antecedents.reset(); get_antecedents(source, target, antecedents); - ctx.assign(l, ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), antecedents.size(), antecedents.data(), l))); + ctx.assign(l, ctx.mk_justification(theory_propagation_justification(get_id(), ctx, antecedents.size(), antecedents.data(), l))); } template @@ -591,12 +591,7 @@ namespace smt { get_antecedents(target, source, antecedents); if (l != null_literal) antecedents.push_back(l); - region & r = ctx.get_region(); - ctx.set_conflict(ctx.mk_justification(theory_conflict_justification(get_id(), r, antecedents.size(), antecedents.data()))); - - if (dump_lemmas()) { - ctx.display_lemma_as_smt_problem(antecedents.size(), antecedents.data(), false_literal); - } + ctx.set_conflict(ctx.mk_justification(theory_conflict_justification(get_id(), ctx, antecedents.size(), antecedents.data()))); return; } diff --git a/src/smt/theory_diff_logic.h b/src/smt/theory_diff_logic.h index 99e70bd74..a613c8012 100644 --- a/src/smt/theory_diff_logic.h +++ b/src/smt/theory_diff_logic.h @@ -350,8 +350,6 @@ namespace smt { bool propagate_eqs() const { return m_params.m_arith_propagate_eqs; } - bool dump_lemmas() const { return m_params.m_arith_dump_lemmas; } - theory_var expand(bool pos, theory_var v, rational & k); void new_eq_or_diseq(bool is_eq, theory_var v1, theory_var v2, justification& eq_just); diff --git a/src/smt/theory_diff_logic_def.h b/src/smt/theory_diff_logic_def.h index 80fbe4022..807eb6cb6 100644 --- a/src/smt/theory_diff_logic_def.h +++ b/src/smt/theory_diff_logic_def.h @@ -665,10 +665,6 @@ void theory_diff_logic::new_edge(dl_var src, dl_var dst, unsigned num_edges params.size(), params.data()); } ctx.mk_clause(lits.size(), lits.data(), js, CLS_TH_LEMMA, nullptr); - if (dump_lemmas()) { - symbol logic(m_lia_or_lra == is_lia ? "QF_LIA" : "QF_LRA"); - ctx.display_lemma_as_smt_problem(lits.size(), lits.data(), false_literal, logic); - } #if 0 TRACE("arith", @@ -707,11 +703,6 @@ void theory_diff_logic::set_neg_cycle_conflict() { for (literal lit : lits) ctx.display_literal_info(tout, lit); tout << "\n";); - if (dump_lemmas()) { - symbol logic(m_lia_or_lra == is_lia ? "QF_LIA" : "QF_LRA"); - ctx.display_lemma_as_smt_problem(lits.size(), lits.data(), false_literal, logic); - } - vector params; if (m.proofs_enabled()) { params.push_back(parameter(symbol("farkas"))); @@ -723,7 +714,7 @@ void theory_diff_logic::set_neg_cycle_conflict() { ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), + get_id(), ctx, lits.size(), lits.data(), 0, nullptr, params.size(), params.data()))); } diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 0b9f4c072..642103f73 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -607,7 +607,7 @@ class theory_lra::imp { return v; } - bool const has_int() const { return lp().has_int_var(); } + bool has_int() const { return lp().has_int_var(); } lpvar register_theory_var_in_lar_solver(theory_var v) { lpvar lpv = lp().external_to_local(v); @@ -1241,6 +1241,7 @@ public: context& c = ctx(); if (!k.is_zero()) { mk_axiom(eq); + m_arith_eq_adapter.mk_axioms(th.ensure_enode(mod_r), th.ensure_enode(p)); mk_axiom(mk_literal(a.mk_ge(mod, zero))); mk_axiom(mk_literal(a.mk_le(mod, upper))); @@ -2331,7 +2332,6 @@ public: literal_vector m_core2; void assign(literal lit, literal_vector const& core, svector const& eqs, vector const& params) { - dump_assign(lit, core, eqs); if (core.size() < small_lemma_size() && eqs.empty()) { m_core2.reset(); for (auto const& c : core) { @@ -2349,7 +2349,7 @@ public: ctx().assign( lit, ctx().mk_justification( ext_theory_propagation_justification( - get_id(), ctx().get_region(), core.size(), core.data(), + get_id(), ctx(), core.size(), core.data(), eqs.size(), eqs.data(), lit, params.size(), params.data()))); } } @@ -2942,8 +2942,6 @@ public: } - bool dump_lemmas() const { return params().m_arith_dump_lemmas; } - bool propagate_eqs() const { return params().m_arith_propagate_eqs && m_num_conflicts < params().m_arith_propagation_threshold; } bound_prop_mode propagation_mode() const { return m_num_conflicts < params().m_arith_propagation_threshold ? params().m_arith_bound_prop : bound_prop_mode::BP_NONE; } @@ -3079,7 +3077,7 @@ public: justification* js = ctx().mk_justification( ext_theory_eq_propagation_justification( - get_id(), ctx().get_region(), m_core.size(), m_core.data(), m_eqs.size(), m_eqs.data(), x, y)); + get_id(), ctx(), m_core.size(), m_core.data(), m_eqs.size(), m_eqs.data(), x, y)); TRACE("arith", for (auto c : m_core) @@ -3203,12 +3201,11 @@ public: set_evidence(ev.ci(), m_core, m_eqs); // SASSERT(validate_conflict(m_core, m_eqs)); - dump_conflict(m_core, m_eqs); if (is_conflict) { ctx().set_conflict( ctx().mk_justification( ext_theory_conflict_justification( - get_id(), ctx().get_region(), + get_id(), ctx(), m_core.size(), m_core.data(), m_eqs.size(), m_eqs.data(), m_params.size(), m_params.data()))); } @@ -3414,11 +3411,6 @@ public: } }; - void dump_conflict(literal_vector const& core, svector const& eqs) { - if (dump_lemmas()) { - ctx().display_lemma_as_smt_problem(core.size(), core.data(), eqs.size(), eqs.data(), false_literal); - } - } bool validate_conflict(literal_vector const& core, svector const& eqs) { if (params().m_arith_mode != arith_solver_id::AS_NEW_ARITH) return true; @@ -3432,13 +3424,6 @@ public: return result; } - void dump_assign(literal lit, literal_vector const& core, svector const& eqs) { - if (dump_lemmas()) { - unsigned id = ctx().display_lemma_as_smt_problem(core.size(), core.data(), eqs.size(), eqs.data(), lit); - (void)id; - } - } - bool validate_assign(literal lit, literal_vector const& core, svector const& eqs) { if (params().m_arith_mode != arith_solver_id::AS_NEW_ARITH) return true; scoped_arith_mode _sa(ctx().get_fparams()); @@ -3523,16 +3508,15 @@ public: case lp::lp_status::OPTIMAL: { init_variable_values(); TRACE("arith", display(tout << st << " v" << v << " vi: " << vi << "\n");); - inf_rational val = get_value(v); - // inf_rational val(term_max.x, term_max.y); + auto val = value(v); blocker = mk_gt(v); - return inf_eps(rational::zero(), val); + return val; } case lp::lp_status::FEASIBLE: { - inf_rational val = get_value(v); + auto val = value(v); TRACE("arith", display(tout << st << " v" << v << " vi: " << vi << "\n");); blocker = mk_gt(v); - return inf_eps(rational::zero(), val); + return val; } default: SASSERT(st == lp::lp_status::UNBOUNDED); @@ -3549,23 +3533,19 @@ public: rational r = val.x; expr_ref e(m); if (a.is_int(obj->get_sort())) { - if (r.is_int()) { + if (r.is_int()) r += rational::one(); - } - else { + else r = ceil(r); - } e = a.mk_numeral(r, obj->get_sort()); e = a.mk_ge(obj, e); } else { e = a.mk_numeral(r, obj->get_sort()); - if (val.y.is_neg()) { + if (val.y.is_neg()) e = a.mk_ge(obj, e); - } - else { + else e = a.mk_gt(obj, e); - } } TRACE("opt", tout << "v" << v << " " << val << " " << r << " " << e << "\n";); return e; diff --git a/src/smt/theory_pb.cpp b/src/smt/theory_pb.cpp index f24e4746d..db076ef8c 100644 --- a/src/smt/theory_pb.cpp +++ b/src/smt/theory_pb.cpp @@ -1106,7 +1106,7 @@ namespace smt { \brief propagate assignment to inequality. This is a basic, non-optimized implementation based on the assumption that inequalities are mostly units - and/or relatively few compared to number of argumets. + and/or relatively few compared to number of arguments. */ void theory_pb::assign_ineq(ineq& c, bool is_true) { m_mpz_trail.push_back(c.m_max_sum); @@ -1486,9 +1486,9 @@ namespace smt { class theory_pb::pb_justification : public theory_propagation_justification { ineq& m_ineq; public: - pb_justification(ineq& c, family_id fid, region & r, + pb_justification(ineq& c, family_id fid, context& ctx, unsigned num_lits, literal const * lits, literal consequent): - theory_propagation_justification(fid, r, num_lits, lits, consequent), + theory_propagation_justification(fid, ctx, num_lits, lits, consequent), m_ineq(c) {} ineq& get_ineq() { return m_ineq; } @@ -1504,7 +1504,7 @@ namespace smt { SASSERT(validate_antecedents(lits)); ctx.assign(l, ctx.mk_justification( pb_justification( - c, get_id(), ctx.get_region(), lits.size(), lits.data(), l))); + c, get_id(), ctx, lits.size(), lits.data(), l))); } @@ -2008,7 +2008,7 @@ namespace smt { SASSERT(validate_antecedents(m_antecedents)); TRACE("pb", tout << "assign " << m_antecedents << " ==> " << alit << "\n";); - ctx.assign(alit, ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), m_antecedents.size(), m_antecedents.data(), alit, 0, nullptr))); + ctx.assign(alit, ctx.mk_justification(theory_propagation_justification(get_id(), ctx, m_antecedents.size(), m_antecedents.data(), alit, 0, nullptr))); DEBUG_CODE( m_antecedents.push_back(~alit); @@ -2029,7 +2029,7 @@ namespace smt { literal lits[2] = { l1, l2 }; justification* js = nullptr; if (proofs_enabled()) { - js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx.get_region(), 2, lits)); + js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx, 2, lits)); } return js; } @@ -2037,7 +2037,7 @@ namespace smt { justification* theory_pb::justify(literal_vector const& lits) { justification* js = nullptr; if (proofs_enabled()) { - js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx.get_region(), lits.size(), lits.data())); + js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx, lits.size(), lits.data())); } return js; } diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 6d8cbc6de..7b0955518 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -304,6 +304,7 @@ void theory_seq::init() { m_ax.add_axiom5 = add_ax; m_ax.mk_eq_empty2 = mk_eq_emp; m_arith_value.init(&ctx); + m_max_unfolding_depth = ctx.get_fparams().m_seq_min_unfolding; } #define TRACEFIN(s) { TRACE("seq", tout << ">>" << s << "\n";); IF_VERBOSE(20, verbose_stream() << s << "\n"); } @@ -418,6 +419,10 @@ final_check_status theory_seq::final_check_eh() { TRACEFIN("fixed_length"); return FC_CONTINUE; } + if (solve_recfuns()) { + TRACEFIN("solve_recfun"); + return FC_CONTINUE; + } if (m_unhandled_expr) { TRACEFIN("give_up"); TRACE("seq", tout << "unhandled: " << mk_pp(m_unhandled_expr, m) << "\n";); @@ -642,11 +647,9 @@ bool theory_seq::check_extensionality() { \brief check negated contains constraints. */ bool theory_seq::check_contains() { - for (unsigned i = 0; !ctx.inconsistent() && i < m_ncs.size(); ++i) { - if (solve_nc(i)) { + for (unsigned i = 0; !ctx.inconsistent() && i < m_ncs.size(); ++i) + if (solve_nc(i)) m_ncs.erase_and_swap(i--); - } - } return m_new_propagation || ctx.inconsistent(); } @@ -783,7 +786,7 @@ bool theory_seq::propagate_lit(dependency* dep, unsigned n, literal const* _lits justification* js = ctx.mk_justification( ext_theory_propagation_justification( - get_id(), ctx.get_region(), lits.size(), lits.data(), eqs.size(), eqs.data(), lit)); + get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), lit)); m_new_propagation = true; ctx.assign(lit, js); @@ -804,14 +807,14 @@ void theory_seq::set_conflict(enode_pair_vector const& eqs, literal_vector const ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), lits.size(), lits.data(), eqs.size(), eqs.data(), 0, nullptr))); + get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), 0, nullptr))); validate_conflict(eqs, lits); } bool theory_seq::propagate_eq(dependency* dep, enode* n1, enode* n2) { - if (n1->get_root() == n2->get_root()) { + if (n1->get_root() == n2->get_root()) return false; - } + literal_vector lits; enode_pair_vector eqs; linearize(dep, eqs, lits); @@ -829,7 +832,7 @@ bool theory_seq::propagate_eq(dependency* dep, enode* n1, enode* n2) { justification* js = ctx.mk_justification( ext_theory_eq_propagation_justification( - get_id(), ctx.get_region(), lits.size(), lits.data(), eqs.size(), eqs.data(), n1, n2)); + get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), n1, n2)); { std::function fn = [&]() { return m.mk_eq(n1->get_expr(), n2->get_expr()); }; @@ -985,9 +988,8 @@ bool theory_seq::is_var(expr* a) const { bool theory_seq::add_solution(expr* l, expr* r, dependency* deps) { - if (l == r) { + if (l == r) return false; - } m_new_solution = true; m_rep.update(l, r, deps); enode* n1 = ensure_enode(l); @@ -1000,11 +1002,11 @@ bool theory_seq::add_solution(expr* l, expr* r, dependency* deps) { } bool theory_seq::propagate_max_length(expr* l, expr* r, dependency* deps) { - unsigned idx; - expr* s; - if (m_util.str.is_empty(l)) { + if (m_util.str.is_empty(l)) std::swap(l, r); - } + + unsigned idx; + expr* s = nullptr; rational hi; if (m_sk.is_tail_u(l, s, idx) && has_length(s) && m_util.str.is_empty(r) && !upper_bound(s, hi)) { propagate_lit(deps, 0, nullptr, m_ax.mk_le(mk_len(s), idx+1)); @@ -1240,12 +1242,31 @@ bool theory_seq::get_length(expr* e, expr_ref& len, literal_vector& lits) { return false; } - +/** + * solve for fold/map (recursive function that depends on a sequence) + * Assumption: the Seq argument of fold/map expands into a concatentation of units + * The assumption is enforced by tracking the length of the seq argument. + * This is ensured in relevant_eh. + * Under the assumption, evern occurrence of fold/map gets simplified by expanding + * arguments. +*/ +bool theory_seq::solve_recfuns() { + for (unsigned i = 0; i < m_recfuns.size() && !ctx.inconsistent(); ++i) { + expr* t = m_recfuns[i]; + dependency* dep = nullptr; + expr_ref r(m); + if (canonize(t, dep, r) && r != t) { + add_solution(t, r, dep); + m_recfuns.erase_and_swap(i--); + } + } + return m_new_propagation || ctx.inconsistent(); +} bool theory_seq::solve_nc(unsigned idx) { nc const& n = m_ncs[idx]; - literal len_gt = n.len_gt(); + literal len_gt = n.len_gt(); expr_ref c(m); expr* a = nullptr, *b = nullptr; VERIFY(m_util.str.is_contains(n.contains(), a, b)); @@ -1263,10 +1284,12 @@ bool theory_seq::solve_nc(unsigned idx) { m_new_propagation = true; return false; case l_false: - break; + if (!m_sk.is_tail(a)) + add_length_limit(a, m_max_unfolding_depth, true); + m_ax.unroll_not_contains(n.contains()); + return true; } - m_ax.unroll_not_contains(n.contains()); - return true; + return false; } theory_seq::cell* theory_seq::mk_cell(cell* p, expr* e, dependency* d) { @@ -1456,7 +1479,7 @@ bool theory_seq::internalize_term(app* term) { mk_var(ctx.get_enode(term)); return true; } - + if (m.is_bool(term) && (m_util.str.is_in_re(term) || m_sk.is_skolem(term))) { bool_var bv = ctx.mk_bool_var(term); @@ -1515,10 +1538,23 @@ void theory_seq::add_length(expr* l) { Add length limit restrictions to sequence s. */ void theory_seq::add_length_limit(expr* s, unsigned k, bool is_searching) { - if (m_sk.is_indexof_left(s)) + if (m_util.str.is_concat(s)) { + for (expr* e : *to_app(s)) + add_length_limit(e, k, is_searching); return; - if (m_sk.is_indexof_right(s)) + } + if (m_util.str.is_unit(s)) return; + if (m_util.str.is_empty(s)) + return; + + if (m_sk.is_skolem(s)) { + for (expr* e : *to_app(s)) + if (m_util.is_seq(e) || m_sk.is_skolem(e)) + add_length_limit(e, k, is_searching); + return; + } + expr_ref lim_e = m_ax.add_length_limit(s, k); unsigned k0 = 0; if (m_length_limit_map.find(s, k0)) { @@ -2500,8 +2536,8 @@ bool theory_seq::expand1(expr* e0, dependency*& eqs, expr_ref& result) { dependency* deps = nullptr; expr* e = m_rep.find(e0, deps); - expr* e1, *e2, *e3; - expr_ref arg1(m), arg2(m); + expr* e1, *e2, *e3, *e4; + expr_ref arg1(m), arg2(m), arg3(m), arg4(m); if (m_util.str.is_concat(e, e1, e2)) { arg1 = try_expand(e1, deps); arg2 = try_expand(e2, deps); @@ -2546,6 +2582,30 @@ bool theory_seq::expand1(expr* e0, dependency*& eqs, expr_ref& result) { if (!arg1 || !arg2) return true; result = m_util.str.mk_index(arg1, arg2, e3); } + else if (m_util.str.is_map(e, e1, e2)) { + arg2 = try_expand(e2, deps); + if (!arg2) return true; + result = m_util.str.mk_map(e1, arg2); + ctx.get_rewriter()(result); + } + else if (m_util.str.is_mapi(e, e1, e2, e3)) { + arg3 = try_expand(e3, deps); + if (!arg3) return true; + result = m_util.str.mk_mapi(e1, e2, arg3); + ctx.get_rewriter()(result); + } + else if (m_util.str.is_foldl(e, e1, e2, e3)) { + arg3 = try_expand(e3, deps); + if (!arg3) return true; + result = m_util.str.mk_foldl(e1, e2, arg3); + ctx.get_rewriter()(result); + } + else if (m_util.str.is_foldli(e, e1, e2, e3, e4)) { + arg4 = try_expand(e4, deps); + if (!arg4) return true; + result = m_util.str.mk_foldli(e1, e2, e3, arg4); + ctx.get_rewriter()(result); + } #if 0 else if (m_util.str.is_nth_i(e, e1, e2)) { arg1 = try_expand(e1, deps); @@ -2889,7 +2949,12 @@ void theory_seq::add_axiom(literal l1, literal l2, literal l3, literal l4, liter } void theory_seq::add_axiom(literal_vector & lits) { - TRACE("seq", ctx.display_literals_smt2(tout << "assert:", lits) << "\n";); + TRACE("seq", ctx.display_literals_verbose(tout << "assert " << lits << " :", lits) << "\n";); + + for (literal lit : lits) + if (ctx.get_assignment(lit) == l_true) + return; + for (literal lit : lits) ctx.mark_as_relevant(lit); @@ -2954,7 +3019,7 @@ bool theory_seq::propagate_eq(dependency* deps, literal_vector const& _lits, exp justification* js = ctx.mk_justification( ext_theory_eq_propagation_justification( - get_id(), ctx.get_region(), lits.size(), lits.data(), eqs.size(), eqs.data(), n1, n2)); + get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), n1, n2)); m_new_propagation = true; @@ -3164,6 +3229,7 @@ void theory_seq::push_scope_eh() { m_nqs.push_scope(); m_ncs.push_scope(); m_lts.push_scope(); + m_recfuns.push_scope(); m_regex.push_scope(); } @@ -3177,6 +3243,7 @@ void theory_seq::pop_scope_eh(unsigned num_scopes) { m_nqs.pop_scope(num_scopes); m_ncs.pop_scope(num_scopes); m_lts.pop_scope(num_scopes); + m_recfuns.pop_scope(num_scopes); m_regex.pop_scope(num_scopes); m_rewrite.reset(); if (ctx.get_base_level() > ctx.get_scope_level() - num_scopes) { @@ -3203,6 +3270,7 @@ void theory_seq::relevant_eh(app* n) { m_util.str.is_from_code(n) || m_util.str.is_to_code(n) || m_util.str.is_unit(n) || + m_util.str.is_last_index(n) || m_util.str.is_length(n) || /* m_util.str.is_replace_all(n) || uncomment to enable axiomatization */ m_util.str.is_le(n)) { @@ -3214,6 +3282,15 @@ void theory_seq::relevant_eh(app* n) { add_int_string(n); } + expr* fn, *acc, *i, *s; + if (m_util.str.is_foldl(n, fn, acc, s) || + m_util.str.is_foldli(n, fn, i, acc, s) || + m_util.str.is_map(n, fn, s) || + m_util.str.is_mapi(n, fn, i, s)) { + add_length_to_eqc(s); + m_recfuns.push_back(n); + } + if (m_util.str.is_ubv2s(n)) add_ubv_string(n); @@ -3276,7 +3353,7 @@ bool theory_seq::should_research(expr_ref_vector & unsat_core) { } } - if (k_min < UINT_MAX/4) { + if (k_min < get_fparams().m_seq_max_unfolding) { m_max_unfolding_depth++; k_min *= 2; if (m_util.is_seq(s_min)) @@ -3290,7 +3367,7 @@ bool theory_seq::should_research(expr_ref_vector & unsat_core) { IF_VERBOSE(1, verbose_stream() << "(smt.seq :increase-depth " << m_max_unfolding_depth << ")\n"); return true; } - else if (k_min != UINT_MAX && k_min >= UINT_MAX/4) { + else if (k_min != UINT_MAX && k_min >= get_fparams().m_seq_max_unfolding) { throw default_exception("reached max unfolding"); } diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index fb5a821d5..49213dbd4 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -222,7 +222,7 @@ namespace smt { class apply { public: - virtual ~apply() {} + virtual ~apply() = default; virtual void operator()(theory_seq& th) = 0; }; @@ -328,6 +328,7 @@ namespace smt { scoped_vector m_nqs; // set of current disequalities. scoped_vector m_ncs; // set of non-contains constraints. scoped_vector m_lts; // set of asserted str.<, str.<= literals + scoped_vector m_recfuns; // set of recursive functions that are defined by unfolding seq argument (map/fold) bool m_lts_checked; unsigned m_eq_id; th_union_find m_find; @@ -484,6 +485,7 @@ namespace smt { bool solve_nqs(unsigned i); bool solve_ne(unsigned i); bool solve_nc(unsigned i); + bool solve_recfuns(); bool check_ne_literals(unsigned idx, unsigned& num_undef_lits); bool propagate_ne2lit(unsigned idx); bool propagate_ne2eq(unsigned idx); diff --git a/src/smt/theory_special_relations.cpp b/src/smt/theory_special_relations.cpp index 837547f72..9113f189e 100644 --- a/src/smt/theory_special_relations.cpp +++ b/src/smt/theory_special_relations.cpp @@ -279,7 +279,7 @@ namespace smt { enode* tcn = ensure_enode(tc_app); if (ctx.get_assignment(tcn) != l_true) { literal consequent = ctx.get_literal(tc_app); - justification* j = ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), 1, &lit, consequent)); + justification* j = ctx.mk_justification(theory_propagation_justification(get_id(), ctx, 1, &lit, consequent)); TRACE("special_relations", tout << "propagate: " << tc_app << "\n";); ctx.assign(consequent, j); new_assertion = true; @@ -469,7 +469,7 @@ namespace smt { ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), + get_id(), ctx, lits.size(), lits.data(), 0, nullptr, params.size(), params.data()))); } @@ -532,7 +532,7 @@ namespace smt { literal_vector const& lits = r.m_explanation; TRACE("special_relations", ctx.display_literals_verbose(tout << mk_pp(x->get_expr(), m) << " = " << mk_pp(y->get_expr(), m) << "\n", lits) << "\n";); IF_VERBOSE(20, ctx.display_literals_verbose(verbose_stream() << mk_pp(x->get_expr(), m) << " = " << mk_pp(y->get_expr(), m) << "\n", lits) << "\n";); - eq_justification js(ctx.mk_justification(ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), lits.size(), lits.data(), 0, nullptr, + eq_justification js(ctx.mk_justification(ext_theory_eq_propagation_justification(get_id(), ctx, lits.size(), lits.data(), 0, nullptr, x, y))); ctx.assign_eq(x, y, js); } @@ -1149,8 +1149,8 @@ namespace smt { void theory_special_relations::get_specrels(func_decl_set& rels) const { - for (auto [f, r] : m_relations) - rels.insert(m_util.get_relation(r->m_decl)); + for (auto [f, r] : m_relations) + rels.insert(r->m_decl); } } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 8bd6c49aa..955241efd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8436,6 +8436,14 @@ namespace smt { } } + bool existNegativeContains = false; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr * a : assignments) { + expr * subterm; + if (m.is_not(a, subterm) && u.str.is_contains(subterm)) existNegativeContains = true; + } + if (!needToAssignFreeVars) { // check string-int terms @@ -8506,9 +8514,11 @@ namespace smt { // we're not done if some variable in a regex membership predicate was unassigned if (regexOK) { if (unused_internal_variables.empty()) { - TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); - m_stats.m_solved_by = 2; - return FC_DONE; + if (!existNegativeContains) { + TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); + m_stats.m_solved_by = 2; + return FC_DONE; + } } else { TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); for (auto const &var : unused_internal_variables) { @@ -8561,9 +8571,6 @@ namespace smt { } TRACE("str", tout << "arithmetic solver done in final check" << std::endl;); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - expr_ref_vector precondition(m); expr_ref_vector cex(m); lbool model_status = fixed_length_model_construction(assignments, precondition, free_variables, candidate_model, cex); diff --git a/src/smt/theory_user_propagator.cpp b/src/smt/theory_user_propagator.cpp index ec0acd903..b8efea851 100644 --- a/src/smt/theory_user_propagator.cpp +++ b/src/smt/theory_user_propagator.cpp @@ -315,7 +315,7 @@ void theory_user_propagator::propagate_consequence(prop_info const& prop) { if (m.is_false(prop.m_conseq)) { js = ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), m_lits.size(), m_lits.data(), m_eqs.size(), m_eqs.data(), 0, nullptr)); + get_id(), ctx, m_lits.size(), m_lits.data(), m_eqs.size(), m_eqs.data(), 0, nullptr)); ctx.set_conflict(js); } else { diff --git a/src/smt/theory_user_propagator.h b/src/smt/theory_user_propagator.h index 73fc5bb45..2ed1acbdf 100644 --- a/src/smt/theory_user_propagator.h +++ b/src/smt/theory_user_propagator.h @@ -143,8 +143,8 @@ namespace smt { char const* get_name() const override { return "user_propagate"; } bool internalize_atom(app* atom, bool gate_ctx) override; bool internalize_term(app* term) override; - void new_eq_eh(theory_var v1, theory_var v2) override { force_push(); if (m_eq_eh) m_eq_eh(m_user_context, this, var2expr(v1), var2expr(v2)); } - void new_diseq_eh(theory_var v1, theory_var v2) override { force_push(); if (m_diseq_eh) m_diseq_eh(m_user_context, this, var2expr(v1), var2expr(v2)); } + void new_eq_eh(theory_var v1, theory_var v2) override { if (m_eq_eh) force_push(), m_eq_eh(m_user_context, this, var2expr(v1), var2expr(v2)); } + void new_diseq_eh(theory_var v1, theory_var v2) override { if (m_diseq_eh) force_push(), m_diseq_eh(m_user_context, this, var2expr(v1), var2expr(v2)); } bool use_diseqs() const override { return ((bool)m_diseq_eh); } bool build_models() const override { return false; } final_check_status final_check_eh() override; diff --git a/src/smt/theory_utvpi_def.h b/src/smt/theory_utvpi_def.h index b43c8a57d..d4c6d684c 100644 --- a/src/smt/theory_utvpi_def.h +++ b/src/smt/theory_utvpi_def.h @@ -204,12 +204,7 @@ namespace smt { inc_conflicts(); literal_vector const& lits = m_nc_functor.get_lits(); IF_VERBOSE(20, ctx.display_literals_smt2(verbose_stream() << "conflict:\n", lits)); - TRACE("utvpi", ctx.display_literals_smt2(tout << "conflict:\n", lits);); - - if (m_params.m_arith_dump_lemmas) { - symbol logic(m_lra ? (m_lia?"QF_LIRA":"QF_LRA") : "QF_LIA"); - ctx.display_lemma_as_smt_problem(lits.size(), lits.data(), false_literal, logic); - } + TRACE("utvpi", ctx.display_literals_smt2(tout << "conflict:\n", lits);); vector params; if (m.proofs_enabled()) { @@ -222,7 +217,7 @@ namespace smt { ctx.set_conflict( ctx.mk_justification( ext_theory_conflict_justification( - get_id(), ctx.get_region(), + get_id(), ctx, lits.size(), lits.data(), 0, nullptr, params.size(), params.data()))); m_nc_functor.reset(); diff --git a/src/smt/theory_wmaxsat.cpp b/src/smt/theory_wmaxsat.cpp index d6c6793e1..c5960a7b9 100644 --- a/src/smt/theory_wmaxsat.cpp +++ b/src/smt/theory_wmaxsat.cpp @@ -283,7 +283,7 @@ namespace smt { ctx.set_conflict( ctx.mk_justification( - ext_theory_conflict_justification(get_id(), ctx.get_region(), lits.size(), lits.data(), 0, nullptr, 0, nullptr))); + ext_theory_conflict_justification(get_id(), ctx, lits.size(), lits.data(), 0, nullptr, 0, nullptr))); } bool theory_wmaxsat::max_unassigned_is_blocked() { @@ -328,10 +328,9 @@ namespace smt { ctx.display_literals_verbose(tout, lits.size(), lits.data()); ctx.display_literal_verbose(tout << " --> ", lit);); - region& r = ctx.get_region(); ctx.assign(lit, ctx.mk_justification( ext_theory_propagation_justification( - get_id(), r, lits.size(), lits.data(), 0, nullptr, lit, 0, nullptr))); + get_id(), ctx, lits.size(), lits.data(), 0, nullptr, lit, 0, nullptr))); } diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 67549c46b..e259adc3e 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -17,7 +17,5 @@ z3_add_component(solver PYG_FILES combined_solver_params.pyg parallel_params.pyg - PYG_FILES - solver_params.pyg ) diff --git a/src/solver/assertions/asserted_formulas.cpp b/src/solver/assertions/asserted_formulas.cpp index e1345fe92..5dec90ba7 100644 --- a/src/solver/assertions/asserted_formulas.cpp +++ b/src/solver/assertions/asserted_formulas.cpp @@ -26,6 +26,7 @@ Revision History: #include "ast/pattern/pattern_inference.h" #include "ast/macros/quasi_macros.h" #include "ast/occurs.h" +#include "ast/bv_decl_plugin.h" #include "solver/assertions/asserted_formulas.h" @@ -54,6 +55,7 @@ asserted_formulas::asserted_formulas(ast_manager & m, smt_params & sp, params_re m_elim_bvs_from_quantifiers(*this), m_cheap_quant_fourier_motzkin(*this), m_apply_bit2int(*this), + m_bv_size_reduce(*this), m_lift_ite(*this), m_ng_lift_ite(*this), m_find_macros(*this), @@ -205,6 +207,7 @@ void asserted_formulas::push_scope_core() { m_elim_term_ite.push(); m_bv_sharing.push_scope(); m_macro_manager.push_scope(); + m_bv_size_reduce.push_scope(); commit(); TRACE("asserted_formulas_scopes", tout << "after push: " << m_scopes.size() << "\n"); } @@ -225,6 +228,7 @@ void asserted_formulas::pop_scope(unsigned num_scopes) { TRACE("asserted_formulas_scopes", tout << "before pop " << num_scopes << " of " << m_scopes.size() << "\n";); m_bv_sharing.pop_scope(num_scopes); m_macro_manager.pop_scope(num_scopes); + m_bv_size_reduce.pop_scope(num_scopes); unsigned new_lvl = m_scopes.size() - num_scopes; scope & s = m_scopes[new_lvl]; m_inconsistent = s.m_inconsistent_old; @@ -293,6 +297,7 @@ void asserted_formulas::reduce() { if (!invoke(m_find_macros)) return; if (!invoke(m_apply_quasi_macros)) return; if (!invoke(m_apply_bit2int)) return; + if (!invoke(m_bv_size_reduce)) return; if (!invoke(m_cheap_quant_fourier_motzkin)) return; if (!invoke(m_pattern_inference)) return; if (!invoke(m_max_bv_sharing_fn)) return; @@ -717,6 +722,40 @@ void asserted_formulas::refine_inj_axiom_fn::simplify(justified_expr const& j, e } +void asserted_formulas::bv_size_reduce_fn::simplify(justified_expr const& j, expr_ref& n, proof_ref& p) { + bv_util bv(m); + expr* f = j.get_fml(); + expr* a, *b, *x; + unsigned lo, hi; + rational r; + expr_ref new_term(m); + auto check_reduce = [&](expr* a, expr* b) { + if (bv.is_extract(a, lo, hi, x) && lo > 0 && hi + 1 == bv.get_bv_size(x) && bv.is_numeral(b, r) && r == 0) { + // insert x -> x[0,lo-1] ++ n into sub + new_term = bv.mk_concat(b, bv.mk_extract(lo - 1, 0, x)); + m_sub.insert(x, new_term); + n = j.get_fml(); + return true; + } + return false; + }; + if (m.is_eq(f, a, b) && (check_reduce(a, b) || check_reduce(b, a))) { + // done + } + else { + n = j.get_fml(); + m_sub(n); + } +} + +void asserted_formulas::bv_size_reduce_fn::push_scope() { + m_sub.push_scope(); +} + +void asserted_formulas::bv_size_reduce_fn::pop_scope(unsigned n) { + m_sub.pop_scope(n); +} + unsigned asserted_formulas::get_total_size() const { expr_mark visited; unsigned r = 0; diff --git a/src/solver/assertions/asserted_formulas.h b/src/solver/assertions/asserted_formulas.h index 77c670f78..c4da97704 100644 --- a/src/solver/assertions/asserted_formulas.h +++ b/src/solver/assertions/asserted_formulas.h @@ -30,6 +30,7 @@ Revision History: #include "ast/rewriter/bv_elim.h" #include "ast/rewriter/der.h" #include "ast/rewriter/elim_bounds.h" +#include "ast/rewriter/expr_safe_replace.h" #include "ast/macros/macro_manager.h" #include "ast/macros/macro_finder.h" #include "ast/normal_forms/defined_names.h" @@ -150,6 +151,16 @@ class asserted_formulas { void post_op() override { af.m_reduce_asserted_formulas(); } }; + class bv_size_reduce_fn : public simplify_fmls { + expr_safe_replace m_sub; + public: + bv_size_reduce_fn(asserted_formulas& af): simplify_fmls(af, "bv-size-reduce"), m_sub(af.m) {} + bool should_apply() const override { return af.m_smt_params.m_bv_size_reduce; } + void simplify(justified_expr const& j, expr_ref& n, proof_ref& p) override; + void push_scope(); + void pop_scope(unsigned n); + }; + class elim_term_ite_fn : public simplify_fmls { elim_term_ite_rw m_elim; public: @@ -205,6 +216,7 @@ class asserted_formulas { elim_bvs_from_quantifiers m_elim_bvs_from_quantifiers; cheap_quant_fourier_motzkin m_cheap_quant_fourier_motzkin; apply_bit2int m_apply_bit2int; + bv_size_reduce_fn m_bv_size_reduce; lift_ite m_lift_ite; ng_lift_ite m_ng_lift_ite; find_macros_fn m_find_macros; diff --git a/src/solver/check_sat_result.h b/src/solver/check_sat_result.h index 53ffd2a1d..86941f590 100644 --- a/src/solver/check_sat_result.h +++ b/src/solver/check_sat_result.h @@ -45,7 +45,7 @@ protected: double m_time; public: check_sat_result():m_ref_count(0), m_status(l_undef), m_time(0) {} - virtual ~check_sat_result() {} + virtual ~check_sat_result() = default; void inc_ref() { m_ref_count++; } void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) dealloc(this); } lbool set_status(lbool r) { return m_status = r; } diff --git a/src/solver/progress_callback.h b/src/solver/progress_callback.h index e8c463609..f49609daf 100644 --- a/src/solver/progress_callback.h +++ b/src/solver/progress_callback.h @@ -20,7 +20,7 @@ Revision History: class progress_callback { public: - virtual ~progress_callback() {} + virtual ~progress_callback() = default; // Called on every check for resource limit exceeded (much more frequent). virtual void fast_progress_sample() {} diff --git a/src/solver/solver.cpp b/src/solver/solver.cpp index d14648058..d582ec2db 100644 --- a/src/solver/solver.cpp +++ b/src/solver/solver.cpp @@ -24,7 +24,7 @@ Notes: #include "ast/display_dimacs.h" #include "tactic/model_converter.h" #include "solver/solver.h" -#include "solver/solver_params.hpp" +#include "params/solver_params.hpp" #include "model/model_evaluator.h" #include "model/model_params.hpp" diff --git a/src/solver/solver.h b/src/solver/solver.h index 09daf3b29..8b7d56a8c 100644 --- a/src/solver/solver.h +++ b/src/solver/solver.h @@ -29,7 +29,7 @@ class model_converter; class solver_factory { public: - virtual ~solver_factory() {} + virtual ~solver_factory() = default; virtual solver * operator()(ast_manager & m, params_ref const & p, bool proofs_enabled, bool models_enabled, bool unsat_core_enabled, symbol const & logic) = 0; }; @@ -113,7 +113,7 @@ public: virtual void set_phase(expr* e) = 0; virtual void move_to_front(expr* e) = 0; - class phase { public: virtual ~phase() {} }; + class phase { public: virtual ~phase() = default; }; virtual phase* get_phase() = 0; diff --git a/src/tactic/aig/aig.cpp b/src/tactic/aig/aig.cpp index b689a53e2..433120b48 100644 --- a/src/tactic/aig/aig.cpp +++ b/src/tactic/aig/aig.cpp @@ -18,7 +18,8 @@ Notes: --*/ #include "tactic/aig/aig.h" #include "tactic/goal.h" -#include "ast/ast_smt2_pp.h" +#include "ast/ast_ll_pp.h" +#include "ast/ast_util.h" #define USE_TWO_LEVEL_RULES #define FIRST_NODE_ID (UINT_MAX/2) @@ -431,13 +432,8 @@ struct aig_manager::imp { expr2aig(imp & _m):m(_m) {} ~expr2aig() { - obj_map::iterator it = m_cache.begin(); - obj_map::iterator end = m_cache.end(); - for (; it != end; ++it) { - TRACE("expr2aig", tout << "dec-ref: "; m.display_ref(tout, it->m_value); - tout << " ref-count: " << ref_count(it->m_value) << "\n";); - m.dec_ref(it->m_value); - } + for (auto& [k,v] : m_cache) + m.dec_ref(v); restore_result_stack(0); } @@ -447,7 +443,7 @@ struct aig_manager::imp { } void cache_result(expr * t, aig_lit const & r) { - TRACE("expr2aig", tout << "caching:\n" << mk_ismt2_pp(t, m.m()) << "\n---> "; m.display_ref(tout, r); tout << "\n";); + TRACE("expr2aig", tout << "caching:\n" << mk_bounded_pp(t, m.m()) << "\n---> "; m.display_ref(tout, r); tout << "\n";); SASSERT(!m_cache.contains(t)); m.inc_ref(r); m_cache.insert(t, r); @@ -1009,33 +1005,34 @@ struct aig_manager::imp { r = invert(r); } - void operator()(aig_lit const & l, expr_ref & r) { - naive(l, r); - } - - void operator()(aig_lit const & l, goal & g) { - g.reset(); + void not_naive(aig_lit const& l, expr_ref & r) { sbuffer roots; + expr_ref_vector rs(r.m()); roots.push_back(l); while (!roots.empty()) { aig_lit n = roots.back(); roots.pop_back(); if (n.is_inverted()) { - g.assert_expr(invert(process_root(n.ptr())), nullptr, nullptr); + rs.push_back(invert(process_root(n.ptr()))); continue; } aig * p = n.ptr(); if (m.is_ite(p)) { - g.assert_expr(process_root(p), nullptr, nullptr); + rs.push_back(process_root(p)); continue; } if (is_var(p)) { - g.assert_expr(m.var2expr(p), nullptr, nullptr); + rs.push_back(m.var2expr(p)); continue; } roots.push_back(left(p)); roots.push_back(right(p)); } + r = ::mk_and(rs); + } + + void operator()(aig_lit const & l, expr_ref & r) { + not_naive(l, r); } }; @@ -1534,18 +1531,14 @@ public: } SASSERT(ref_count(r) >= 1); } - catch (const aig_exception & ex) { - dec_ref(r); - throw ex; - } + catch (const aig_exception & ex) { + dec_ref(r); + throw ex; + } dec_ref_result(r); return r; } - void to_formula(aig_lit const & r, goal & g) { - aig2expr proc(*this); - proc(r, g); - } void to_formula(aig_lit const & r, expr_ref & result) { aig2expr proc(*this); @@ -1581,7 +1574,7 @@ public: qhead++; display_ref(out, n); out << ": "; if (is_var(n)) { - out << mk_ismt2_pp(m_var2exprs[n->m_id], m()) << "\n"; + out << mk_bounded_pp(m_var2exprs[n->m_id], m()) << "\n"; } else { display_ref(out, n->m_children[0]); @@ -1607,7 +1600,7 @@ public: if (r.is_inverted()) out << "(not "; if (is_var(r)) { - out << mk_ismt2_pp(var2expr(r.ptr()), m()); + out << mk_bounded_pp(var2expr(r.ptr()), m()); } else { out << "aig" << to_idx(r.ptr()); @@ -1738,11 +1731,6 @@ void aig_manager::max_sharing(aig_ref & r) { r = aig_ref(*this, m_imp->max_sharing(aig_lit(r))); } -void aig_manager::to_formula(aig_ref const & r, goal & g) { - SASSERT(!g.proofs_enabled()); - SASSERT(!g.unsat_core_enabled()); - return m_imp->to_formula(aig_lit(r), g); -} void aig_manager::to_formula(aig_ref const & r, expr_ref & res) { return m_imp->to_formula(aig_lit(r), res); diff --git a/src/tactic/aig/aig_tactic.cpp b/src/tactic/aig/aig_tactic.cpp index 5ffc0dc20..9c5390c16 100644 --- a/src/tactic/aig/aig_tactic.cpp +++ b/src/tactic/aig/aig_tactic.cpp @@ -16,6 +16,9 @@ Author: Notes: --*/ +#include "ast/ast_util.h" +#include "ast/ast_ll_pp.h" +#include "ast/for_each_expr.h" #include "tactic/tactical.h" #include "tactic/aig/aig.h" @@ -24,7 +27,6 @@ class aig_manager; class aig_tactic : public tactic { unsigned long long m_max_memory; bool m_aig_gate_encoding; - bool m_aig_per_assertion; aig_manager * m_aig_manager; struct mk_aig_manager { @@ -52,40 +54,54 @@ public: aig_tactic * t = alloc(aig_tactic); t->m_max_memory = m_max_memory; t->m_aig_gate_encoding = m_aig_gate_encoding; - t->m_aig_per_assertion = m_aig_per_assertion; return t; } void updt_params(params_ref const & p) override { m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); m_aig_gate_encoding = p.get_bool("aig_default_gate_encoding", true); - m_aig_per_assertion = p.get_bool("aig_per_assertion", true); } void collect_param_descrs(param_descrs & r) override { insert_max_memory(r); - r.insert("aig_per_assertion", CPK_BOOL, "(default: true) process one assertion at a time."); } void operator()(goal_ref const & g) { + ast_manager& m = g->m(); + mk_aig_manager mk(*this, m); - mk_aig_manager mk(*this, g->m()); - if (m_aig_per_assertion) { - for (unsigned i = 0; i < g->size(); i++) { + expr_ref_vector nodeps(m); + + for (unsigned i = 0; i < g->size(); i++) { + expr_dependency * ed = g->dep(i); + if (!ed) { + nodeps.push_back(g->form(i)); + g->update(i, m.mk_true()); + } + else { aig_ref r = m_aig_manager->mk_aig(g->form(i)); m_aig_manager->max_sharing(r); - expr_ref new_f(g->m()); + expr_ref new_f(m); m_aig_manager->to_formula(r, new_f); - expr_dependency * ed = g->dep(i); - g->update(i, new_f, nullptr, ed); + unsigned old_sz = get_num_exprs(g->form(i)); + unsigned new_sz = get_num_exprs(new_f); + if (new_sz <= 1.2*old_sz) + g->update(i, new_f, nullptr, ed); } } - else { - fail_if_unsat_core_generation("aig", g); - aig_ref r = m_aig_manager->mk_aig(*(g.get())); - g->reset(); // save memory + + if (!nodeps.empty()) { + expr_ref conj(::mk_and(nodeps)); + aig_ref r = m_aig_manager->mk_aig(conj); m_aig_manager->max_sharing(r); - m_aig_manager->to_formula(r, *(g.get())); + expr_ref new_f(m); + m_aig_manager->to_formula(r, new_f); + unsigned old_sz = get_num_exprs(conj); + unsigned new_sz = get_num_exprs(new_f); + + if (new_sz > 1.2*old_sz) + new_f = conj; + g->assert_expr(new_f); } } diff --git a/src/tactic/bv/bv_bound_chk_tactic.cpp b/src/tactic/bv/bv_bound_chk_tactic.cpp index b0e6be1bd..3a2f85831 100644 --- a/src/tactic/bv/bv_bound_chk_tactic.cpp +++ b/src/tactic/bv/bv_bound_chk_tactic.cpp @@ -147,7 +147,7 @@ public: imp(ast_manager & m, params_ref const & p, bv_bound_chk_stats& stats) : m_rw(m, p, stats) { } - virtual ~imp() { } + virtual ~imp() = default; ast_manager& m() { return m_rw.m(); } diff --git a/src/tactic/bv/bv_size_reduction_tactic.cpp b/src/tactic/bv/bv_size_reduction_tactic.cpp index 18083452b..788f562d3 100644 --- a/src/tactic/bv/bv_size_reduction_tactic.cpp +++ b/src/tactic/bv/bv_size_reduction_tactic.cpp @@ -112,6 +112,7 @@ public: unsigned bv_sz; expr * f, * lhs, * rhs; +#if 0 auto match_bitmask = [&](expr* lhs, expr* rhs) { unsigned lo, hi; expr* arg; @@ -131,7 +132,8 @@ public: update_unsigned_upper(to_app(arg), val); return true; }; - +#endif + for (unsigned i = 0; i < sz; i++) { bool negated = false; f = g.form(i); diff --git a/src/tactic/converter.h b/src/tactic/converter.h index 60602557e..bbd30c351 100644 --- a/src/tactic/converter.h +++ b/src/tactic/converter.h @@ -26,7 +26,7 @@ class converter { unsigned m_ref_count; public: converter():m_ref_count(0) {} - virtual ~converter() {} + virtual ~converter() = default; void inc_ref() { ++m_ref_count; } diff --git a/src/tactic/core/ctx_simplify_tactic.h b/src/tactic/core/ctx_simplify_tactic.h index 0beced06e..c8e34f33d 100644 --- a/src/tactic/core/ctx_simplify_tactic.h +++ b/src/tactic/core/ctx_simplify_tactic.h @@ -26,7 +26,7 @@ public: class simplifier { goal_num_occurs* m_occs; public: - virtual ~simplifier() {} + virtual ~simplifier() = default; virtual bool assert_expr(expr * t, bool sign) = 0; virtual bool simplify(expr* t, expr_ref& result) = 0; virtual bool may_simplify(expr* t) { return true; } diff --git a/src/tactic/core/dom_simplify_tactic.h b/src/tactic/core/dom_simplify_tactic.h index b5d5e1ce9..43e13d961 100644 --- a/src/tactic/core/dom_simplify_tactic.h +++ b/src/tactic/core/dom_simplify_tactic.h @@ -64,7 +64,7 @@ public: class dom_simplifier { public: - virtual ~dom_simplifier() {} + virtual ~dom_simplifier() = default; /** \brief assert_expr performs an implicit push */ diff --git a/src/tactic/probe.h b/src/tactic/probe.h index ae2879163..4d1af66a8 100644 --- a/src/tactic/probe.h +++ b/src/tactic/probe.h @@ -44,7 +44,7 @@ private: public: probe():m_ref_count(0) {} - virtual ~probe() {} + virtual ~probe() = default; void inc_ref() { ++m_ref_count; } void dec_ref() { SASSERT(m_ref_count > 0); --m_ref_count; if (m_ref_count == 0) dealloc(this); } diff --git a/src/tactic/smtlogics/qfbv_tactic.cpp b/src/tactic/smtlogics/qfbv_tactic.cpp index af20eae3e..b242d86cf 100644 --- a/src/tactic/smtlogics/qfbv_tactic.cpp +++ b/src/tactic/smtlogics/qfbv_tactic.cpp @@ -90,9 +90,6 @@ static tactic * mk_qfbv_tactic(ast_manager& m, params_ref const & p, tactic* sat params_ref solver_p; solver_p.set_bool("preprocess", false); // preprocessor of smt::context is not needed. - params_ref big_aig_p; - big_aig_p.set_bool("aig_per_assertion", false); - tactic* preamble_st = mk_qfbv_preamble(m, p); tactic * st = main_p(and_then(preamble_st, // If the user sets HI_DIV0=false, then the formula may contain uninterpreted function @@ -107,10 +104,7 @@ static tactic * mk_qfbv_tactic(ast_manager& m, params_ref const & p, tactic* sat and_then(using_params(and_then(mk_simplify_tactic(m), mk_solve_eqs_tactic(m)), local_ctx_p), - if_no_proofs(cond(mk_produce_unsat_cores_probe(), - mk_aig_tactic(), - using_params(mk_aig_tactic(), - big_aig_p))))), + if_no_proofs(mk_aig_tactic()))), sat), smt)))); diff --git a/src/tactic/user_propagator_base.h b/src/tactic/user_propagator_base.h index 46b5eda8a..18d71b514 100644 --- a/src/tactic/user_propagator_base.h +++ b/src/tactic/user_propagator_base.h @@ -53,7 +53,7 @@ namespace user_propagator { class core { public: - virtual ~core() {} + virtual ~core() = default; virtual void user_propagate_init( void* ctx, diff --git a/src/test/ex.cpp b/src/test/ex.cpp index 444431475..10b5f8a90 100644 --- a/src/test/ex.cpp +++ b/src/test/ex.cpp @@ -21,7 +21,7 @@ Revision History: class ex { public: - virtual ~ex() {} + virtual ~ex() = default; virtual char const * msg() const = 0; }; diff --git a/src/test/lp/smt_reader.h b/src/test/lp/smt_reader.h index e641410db..2ab0c1ea6 100644 --- a/src/test/lp/smt_reader.h +++ b/src/test/lp/smt_reader.h @@ -20,7 +20,7 @@ Revision History: #pragma once -// reads an MPS file reperesenting a Mixed Integer Program +// reads an MPS file representing a Mixed Integer Program #include #include #include diff --git a/src/test/simplex.cpp b/src/test/simplex.cpp index 2e59b4171..3c20956fb 100644 --- a/src/test/simplex.cpp +++ b/src/test/simplex.cpp @@ -149,7 +149,7 @@ static void test5() { add(M, vec(0, 0, 0, 0, 0, 0)); M.display(std::cout); vector> K; - kernel(M, K); + kernel_ffe(M, K); std::cout << "after\n"; for (auto const& v : K) std::cout << v << "\n"; @@ -157,6 +157,39 @@ static void test5() { } +static void test6() { + unsynch_mpq_manager m; + qmatrix M(m); + add(M, vec(-1, 2, 1)); + add(M, vec(0, 1, 1)); + M.display(std::cout); + vector> K; + kernel_ffe(M, K); + std::cout << "Kernel:\n"; + for (auto const &v : K) + std::cout << v << "\n"; + std::cout << "matrix after\n"; + M.display(std::cout); + +} + +static void test7() { + unsynch_mpq_manager m; + qmatrix M(m); + add(M, vec(1, 2, 3, 4, 10)); + add(M, vec(2, 2, 3, 4, 11)); + add(M, vec(3, 3, 3, 4, 13)); + add(M, vec(9, 8, 7, 6, 30)); + M.display(std::cout); + vector> K; + kernel_ffe(M, K); + std::cout << "Kernel:\n"; + for (auto const &v : K) + std::cout << v << "\n"; + std::cout << "matrix after\n"; + M.display(std::cout); +} + void tst_simplex() { reslimit rl; Simplex S(rl); @@ -193,4 +226,6 @@ void tst_simplex() { test3(); test4(); test5(); + test6(); + test7(); } diff --git a/src/util/cmd_context_types.h b/src/util/cmd_context_types.h index 80e284d90..b8c3d4758 100644 --- a/src/util/cmd_context_types.h +++ b/src/util/cmd_context_types.h @@ -78,7 +78,7 @@ typedef std::pair sorted_var; /** \brief Command abstract class. - Commands may have variable number of argumets. + Commands may have variable number of arguments. */ class cmd { symbol m_name; @@ -87,7 +87,7 @@ protected: int m_pos; public: cmd(char const * n):m_name(n), m_line(0), m_pos(0) {} - virtual ~cmd() {} + virtual ~cmd() = default; virtual void reset(cmd_context & ctx) {} virtual void finalize(cmd_context & ctx) {} virtual symbol get_name() const { return m_name; } diff --git a/src/util/env_params.cpp b/src/util/env_params.cpp index db2c9a890..80a1195aa 100644 --- a/src/util/env_params.cpp +++ b/src/util/env_params.cpp @@ -28,7 +28,10 @@ void env_params::updt_params() { enable_warning_messages(p.get_bool("warning", true)); memory::set_max_size(megabytes_to_bytes(p.get_uint("memory_max_size", 0))); memory::set_max_alloc_count(p.get_uint("memory_max_alloc_count", 0)); - memory::set_high_watermark(megabytes_to_bytes(p.get_uint("memory_high_watermark", 0))); + memory::set_high_watermark(p.get_uint("memory_high_watermark", 0)); + unsigned mb = p.get_uint("memory_high_watermark_mb", 0); + if (mb > 0) + memory::set_high_watermark(megabytes_to_bytes(mb)); } void env_params::collect_param_descrs(param_descrs & d) { @@ -36,5 +39,6 @@ void env_params::collect_param_descrs(param_descrs & d) { d.insert("warning", CPK_BOOL, "enable/disable warning messages", "true"); d.insert("memory_max_size", CPK_UINT, "set hard upper limit for memory consumption (in megabytes), if 0 then there is no limit", "0"); d.insert("memory_max_alloc_count", CPK_UINT, "set hard upper limit for memory allocations, if 0 then there is no limit", "0"); - d.insert("memory_high_watermark", CPK_UINT, "set high watermark for memory consumption (in megabytes), if 0 then there is no limit", "0"); + d.insert("memory_high_watermark", CPK_UINT, "set high watermark for memory consumption (in bytes), if 0 then there is no limit", "0"); + d.insert("memory_high_watermark_mb", CPK_UINT, "set high watermark for memory consumption (in megabytes), if 0 then there is no limit", "0"); } diff --git a/src/util/event_handler.h b/src/util/event_handler.h index 8993162e6..cabbca4c9 100644 --- a/src/util/event_handler.h +++ b/src/util/event_handler.h @@ -31,7 +31,7 @@ protected: event_handler_caller_t m_caller_id; public: event_handler(): m_caller_id(UNSET_EH_CALLER) {} - virtual ~event_handler() {} + virtual ~event_handler() = default; virtual void operator()(event_handler_caller_t caller_id) = 0; event_handler_caller_t caller_id() const { return m_caller_id; } }; diff --git a/src/util/gparams.cpp b/src/util/gparams.cpp index e7c8a4d53..839d02ab0 100644 --- a/src/util/gparams.cpp +++ b/src/util/gparams.cpp @@ -533,6 +533,27 @@ public: out << "\n"; d->display(out, 4, false); } + void display_module_markdown(std::ostream & out, char const* module_name) { + lock_guard lock(*gparams_mux); + param_descrs * d = nullptr; + + if (module_name == std::string("global")) { + out << "\n## Global Parameters\n\n"; + get_param_descrs().display_markdown(out); + return; + } + if (!get_module_param_descr(module_name, d)) { + std::stringstream strm; + strm << "unknown module '" << module_name << "'"; + throw exception(std::move(strm).str()); + } + out << "\n## " << module_name << "\n\n"; + char const * descr = nullptr; + if (get_module_descrs().find(module_name, descr)) + out << descr << "\n"; + out << "\n"; + d->display_markdown(out); + } param_descrs const& get_global_param_descrs() { lock_guard lock(*gparams_mux); @@ -643,6 +664,11 @@ void gparams::display_module(std::ostream & out, char const * module_name) { g_imp->display_module(out, module_name); } +void gparams::display_module_markdown(std::ostream & out, char const * module_name) { + SASSERT(g_imp); + g_imp->display_module_markdown(out, module_name); +} + void gparams::display_parameter(std::ostream & out, char const * name) { SASSERT(g_imp); g_imp->display_parameter(out, name); diff --git a/src/util/gparams.h b/src/util/gparams.h index 0bd5e4d60..4cbad621f 100644 --- a/src/util/gparams.h +++ b/src/util/gparams.h @@ -119,6 +119,7 @@ public: // Auxiliary APIs for better command line support static void display_modules(std::ostream & out); static void display_module(std::ostream & out, char const * module_name); + static void display_module_markdown(std::ostream & out, char const * module_name); static void display_parameter(std::ostream & out, char const * name); static param_descrs const& get_global_param_descrs(); diff --git a/src/util/memory_manager.cpp b/src/util/memory_manager.cpp index 67f5b82b3..85d673f7c 100644 --- a/src/util/memory_manager.cpp +++ b/src/util/memory_manager.cpp @@ -13,6 +13,11 @@ Copyright (c) 2015 Microsoft Corporation #include "util/error_codes.h" #include "util/debug.h" #include "util/scoped_timer.h" +#ifdef __GLIBC__ +# include +# define HAS_MALLOC_USABLE_SIZE +#endif + // The following two function are automatically generated by the mk_make.py script. // The script collects ADD_INITIALIZER and ADD_FINALIZER commands in the .h files. // For example, rational.h contains @@ -215,7 +220,7 @@ void * memory::allocate(char const* file, int line, char const* obj, size_t s) { } #endif -#if !defined(SINGLE_THREAD) +#ifndef SINGLE_THREAD // ================================== // ================================== // THREAD LOCAL VERSION @@ -258,9 +263,14 @@ static void synchronize_counters(bool allocating) { } void memory::deallocate(void * p) { +#ifdef HAS_MALLOC_USABLE_SIZE + size_t sz = malloc_usable_size(p); + void * real_p = p; +#else size_t * sz_p = reinterpret_cast(p) - 1; size_t sz = *sz_p; void * real_p = reinterpret_cast(sz_p); +#endif g_memory_thread_alloc_size -= sz; free(real_p); if (g_memory_thread_alloc_size < -SYNCH_THRESHOLD) { @@ -269,28 +279,41 @@ void memory::deallocate(void * p) { } void * memory::allocate(size_t s) { +#ifndef HAS_MALLOC_USABLE_SIZE s = s + sizeof(size_t); // we allocate an extra field! - void * r = malloc(s); - if (r == nullptr) { - throw_out_of_memory(); - return nullptr; - } - *(static_cast(r)) = s; +#endif g_memory_thread_alloc_size += s; g_memory_thread_alloc_count += 1; if (g_memory_thread_alloc_size > SYNCH_THRESHOLD) { synchronize_counters(true); } - + void * r = malloc(s); + if (r == nullptr) { + throw_out_of_memory(); + return nullptr; + } +#ifdef HAS_MALLOC_USABLE_SIZE + g_memory_thread_alloc_size += malloc_usable_size(r) - s; + return r; +#else + *(static_cast(r)) = s; return static_cast(r) + 1; // we return a pointer to the location after the extra field +#endif } void* memory::reallocate(void *p, size_t s) { +#ifdef HAS_MALLOC_USABLE_SIZE + size_t sz = malloc_usable_size(p); + void * real_p = p; + // We may be lucky and malloc gave us enough space + if (sz >= s) + return p; +#else size_t *sz_p = reinterpret_cast(p)-1; size_t sz = *sz_p; void *real_p = reinterpret_cast(sz_p); s = s + sizeof(size_t); // we allocate an extra field! - +#endif g_memory_thread_alloc_size += s - sz; g_memory_thread_alloc_count += 1; if (g_memory_thread_alloc_size > SYNCH_THRESHOLD) { @@ -302,74 +325,97 @@ void* memory::reallocate(void *p, size_t s) { throw_out_of_memory(); return nullptr; } +#ifdef HAS_MALLOC_USABLE_SIZE + g_memory_thread_alloc_size += malloc_usable_size(r) - s; + return r; +#else *(static_cast(r)) = s; return static_cast(r) + 1; // we return a pointer to the location after the extra field +#endif } #else // ================================== // ================================== -// NO THREAD LOCAL VERSION +// SINGLE-THREAD MODE // ================================== // ================================== -// allocate & deallocate without using thread local storage +// allocate & deallocate without locking void memory::deallocate(void * p) { +#ifdef HAS_MALLOC_USABLE_SIZE + size_t sz = malloc_usable_size(p); + void * real_p = p; +#else size_t * sz_p = reinterpret_cast(p) - 1; size_t sz = *sz_p; void * real_p = reinterpret_cast(sz_p); - { - lock_guard lock(*g_memory_mux); - g_memory_alloc_size -= sz; - } +#endif + g_memory_alloc_size -= sz; free(real_p); } void * memory::allocate(size_t s) { +#ifndef HAS_MALLOC_USABLE_SIZE s = s + sizeof(size_t); // we allocate an extra field! - { - lock_guard lock(*g_memory_mux); - g_memory_alloc_size += s; - g_memory_alloc_count += 1; - if (g_memory_alloc_size > g_memory_max_used_size) - g_memory_max_used_size = g_memory_alloc_size; - if (g_memory_max_size != 0 && g_memory_alloc_size > g_memory_max_size) - throw_out_of_memory(); - if (g_memory_max_alloc_count != 0 && g_memory_alloc_count > g_memory_max_alloc_count) - throw_alloc_counts_exceeded(); - } +#endif + g_memory_alloc_size += s; + g_memory_alloc_count += 1; + if (g_memory_alloc_size > g_memory_max_used_size) + g_memory_max_used_size = g_memory_alloc_size; + if (g_memory_max_size != 0 && g_memory_alloc_size > g_memory_max_size) + throw_out_of_memory(); + if (g_memory_max_alloc_count != 0 && g_memory_alloc_count > g_memory_max_alloc_count) + throw_alloc_counts_exceeded(); + void * r = malloc(s); if (r == nullptr) { throw_out_of_memory(); return nullptr; } +#ifdef HAS_MALLOC_USABLE_SIZE + g_memory_alloc_size += malloc_usable_size(r) - s; + return r; +#else *(static_cast(r)) = s; return static_cast(r) + 1; // we return a pointer to the location after the extra field +#endif } void* memory::reallocate(void *p, size_t s) { +#ifdef HAS_MALLOC_USABLE_SIZE + size_t sz = malloc_usable_size(p); + void * real_p = p; + // We may be lucky and malloc gave us enough space + if (sz >= s) + return p; +#else size_t * sz_p = reinterpret_cast(p) - 1; size_t sz = *sz_p; void * real_p = reinterpret_cast(sz_p); s = s + sizeof(size_t); // we allocate an extra field! - { - lock_guard lock(*g_memory_mux); - g_memory_alloc_size += s - sz; - g_memory_alloc_count += 1; - if (g_memory_alloc_size > g_memory_max_used_size) - g_memory_max_used_size = g_memory_alloc_size; - if (g_memory_max_size != 0 && g_memory_alloc_size > g_memory_max_size) - throw_out_of_memory(); - if (g_memory_max_alloc_count != 0 && g_memory_alloc_count > g_memory_max_alloc_count) - throw_alloc_counts_exceeded(); - } +#endif + g_memory_alloc_size += s - sz; + g_memory_alloc_count += 1; + if (g_memory_alloc_size > g_memory_max_used_size) + g_memory_max_used_size = g_memory_alloc_size; + if (g_memory_max_size != 0 && g_memory_alloc_size > g_memory_max_size) + throw_out_of_memory(); + if (g_memory_max_alloc_count != 0 && g_memory_alloc_count > g_memory_max_alloc_count) + throw_alloc_counts_exceeded(); + void *r = realloc(real_p, s); if (r == nullptr) { throw_out_of_memory(); return nullptr; } +#ifdef HAS_MALLOC_USABLE_SIZE + g_memory_alloc_size += malloc_usable_size(r) - s; + return r; +#else *(static_cast(r)) = s; return static_cast(r) + 1; // we return a pointer to the location after the extra field +#endif } #endif diff --git a/src/util/mpq.cpp b/src/util/mpq.cpp index ed73e21f0..324750cfa 100644 --- a/src/util/mpq.cpp +++ b/src/util/mpq.cpp @@ -20,10 +20,6 @@ Revision History: #include "util/warning.h" #include "util/z3_exception.h" -template -mpq_manager::mpq_manager() { -} - template mpq_manager::~mpq_manager() { del(m_tmp1); diff --git a/src/util/mpq.h b/src/util/mpq.h index 1b97b6ece..f1afcbe50 100644 --- a/src/util/mpq.h +++ b/src/util/mpq.h @@ -115,7 +115,7 @@ public: static bool precise() { return true; } static bool field() { return true; } - mpq_manager(); + mpq_manager() = default; ~mpq_manager(); diff --git a/src/util/mpz.cpp b/src/util/mpz.cpp index d3e9941d3..316a1bba1 100644 --- a/src/util/mpz.cpp +++ b/src/util/mpz.cpp @@ -41,7 +41,6 @@ Revision History: #if defined(_MP_GMP) // Use LEHMER only if not using GMP -// LEHMER assumes 32-bit digits, so it cannot be used with MSBIGNUM library + 64-bit binary #define EUCLID_GCD #else #define LEHMER_GCD @@ -50,7 +49,7 @@ Revision History: #if defined(__GNUC__) #define _trailing_zeros32(X) __builtin_ctz(X) -#elif defined(_WINDOWS) && !defined(_M_ARM) && !defined(_M_ARM64) && !defined(__MINGW32__) +#elif defined(_WINDOWS) && (defined(_M_X86) || (defined(_M_X64) && !defined(_M_ARM64EC))) // This is needed for _tzcnt_u32 and friends. #include #define _trailing_zeros32(X) _tzcnt_u32(X) @@ -62,7 +61,7 @@ static uint32_t _trailing_zeros32(uint32_t x) { } #endif -#if (defined(__LP64__) || defined(_WIN64)) && !defined(_M_ARM) && !defined(_M_ARM64) +#if (defined(__LP64__) || defined(_WIN64)) && defined(_M_X64) && !defined(_M_ARM64EC) #if defined(__GNUC__) #define _trailing_zeros64(X) __builtin_ctzll(X) #else diff --git a/src/util/params.cpp b/src/util/params.cpp index 04c70b71e..ee61bf47f 100644 --- a/src/util/params.cpp +++ b/src/util/params.cpp @@ -161,12 +161,16 @@ struct param_descrs::imp { bool operator()(symbol const & s1, symbol const & s2) const { return ::lt(s1, s2); } }; - void display(std::ostream & out, unsigned indent, bool smt2_style, bool include_descr) const { + void display(std::ostream & out, unsigned indent, bool smt2_style, bool include_descr, bool markdown) const { svector names; for (auto const& kv : m_info) { names.push_back(kv.m_key); } std::sort(names.begin(), names.end(), symlt()); + if (markdown) { + out << " Parameter | Type | Description | Default\n"; + out << " ----------|------|-------------|--------\n"; + } for (symbol const& name : names) { for (unsigned i = 0; i < indent; i++) out << " "; if (smt2_style) @@ -186,10 +190,30 @@ struct param_descrs::imp { info d; m_info.find(name, d); SASSERT(d.m_descr); - out << " (" << d.m_kind << ")"; - if (include_descr) + if (markdown) + out << " | " << d.m_kind << " "; + else + out << " (" << d.m_kind << ")"; + if (markdown) { + out << " | "; + std::string desc; + for (auto ch : std::string(d.m_descr)) { + switch (ch) { + case '<': desc += "<"; break; + case '>': desc += ">"; break; + default: desc.push_back(ch); + } + } + out << " " << desc; + } + else if (include_descr) out << " " << d.m_descr; - if (d.m_default != nullptr) + if (markdown) { + out << " | "; + if (d.m_default) + out << d.m_default; + } + else if (d.m_default != nullptr) out << " (default: " << d.m_default << ")"; out << "\n"; } @@ -280,7 +304,11 @@ char const* param_descrs::get_module(symbol const& name) const { } void param_descrs::display(std::ostream & out, unsigned indent, bool smt2_style, bool include_descr) const { - return m_imp->display(out, indent, smt2_style, include_descr); + return m_imp->display(out, indent, smt2_style, include_descr, false); +} + +void param_descrs::display_markdown(std::ostream & out, bool smt2_style, bool include_descr) const { + return m_imp->display(out, 0, smt2_style, include_descr, true); } void insert_max_memory(param_descrs & r) { diff --git a/src/util/params.h b/src/util/params.h index 7ad152a59..bc45bdbce 100644 --- a/src/util/params.h +++ b/src/util/params.h @@ -130,6 +130,7 @@ public: char const * get_default(char const * name) const; char const * get_default(symbol const & name) const; void display(std::ostream & out, unsigned indent = 0, bool smt2_style=false, bool include_descr=true) const; + void display_markdown(std::ostream& out, bool smt2_style = false, bool include_descr = true) const; unsigned size() const; symbol get_param_name(unsigned idx) const; char const * get_module(symbol const& name) const; diff --git a/src/util/rational.h b/src/util/rational.h index cffd0083c..4203a54ea 100644 --- a/src/util/rational.h +++ b/src/util/rational.h @@ -138,21 +138,15 @@ public: m().set(m_val, r.m_val); return *this; } -private: - rational & operator=(bool) { - UNREACHABLE(); return *this; - } - inline rational operator*(bool r1) const { - UNREACHABLE(); - return *this; - } -public: + rational & operator=(bool) = delete; + rational operator*(bool r1) const = delete; + rational & operator=(int v) { m().set(m_val, v); return *this; } - rational & operator=(double v) { UNREACHABLE(); return *this; } + rational & operator=(double v) = delete; friend inline rational numerator(rational const & r) { rational result; m().get_numerator(r.m_val, result.m_val); return result; } diff --git a/src/util/sat_literal.h b/src/util/sat_literal.h index 920b0528b..aeb23bddd 100644 --- a/src/util/sat_literal.h +++ b/src/util/sat_literal.h @@ -110,12 +110,10 @@ namespace sat { inline void negate(literal_vector& ls) { for (unsigned i = 0; i < ls.size(); ++i) ls[i].neg(); } - typedef tracked_uint_set uint_set; - - typedef uint_set bool_var_set; + typedef tracked_uint_set bool_var_set; class literal_set { - uint_set m_set; + tracked_uint_set m_set; public: literal_set(literal_vector const& v) { for (unsigned i = 0; i < v.size(); ++i) insert(v[i]); @@ -141,9 +139,9 @@ namespace sat { void reset() { m_set.reset(); } void finalize() { m_set.finalize(); } class iterator { - uint_set::iterator m_it; + tracked_uint_set::iterator m_it; public: - iterator(uint_set::iterator it):m_it(it) {} + iterator(tracked_uint_set::iterator it):m_it(it) {} literal operator*() const { return to_literal(*m_it); } iterator& operator++() { ++m_it; return *this; } iterator operator++(int) { iterator tmp = *this; ++m_it; return tmp; } diff --git a/src/util/trail.h b/src/util/trail.h index 9a7ec4303..20a525cf7 100644 --- a/src/util/trail.h +++ b/src/util/trail.h @@ -26,7 +26,7 @@ Revision History: class trail { public: - virtual ~trail() {} + virtual ~trail() = default; virtual void undo() = 0; }; diff --git a/src/util/warning.cpp b/src/util/warning.cpp index 1bc91fbfa..033c93780 100644 --- a/src/util/warning.cpp +++ b/src/util/warning.cpp @@ -83,6 +83,10 @@ void set_warning_stream(std::ostream* strm) { g_warning_stream = strm; } +std::ostream* warning_stream() { + return g_warning_stream; +} + void format2ostream(std::ostream & out, char const* msg, va_list args) { svector buff; BEGIN_ERR_HANDLER(); diff --git a/src/util/warning.h b/src/util/warning.h index 5a74ebd2d..9f71d74b3 100644 --- a/src/util/warning.h +++ b/src/util/warning.h @@ -28,6 +28,8 @@ void set_error_stream(std::ostream* strm); void set_warning_stream(std::ostream* strm); +std::ostream* warning_stream(); + void warning_msg(const char * msg, ...); void format2ostream(std::ostream& out, char const* fmt, va_list args); diff --git a/src/util/z3_exception.h b/src/util/z3_exception.h index 1d54fb7b5..b6875257c 100644 --- a/src/util/z3_exception.h +++ b/src/util/z3_exception.h @@ -22,7 +22,7 @@ Notes: class z3_exception { public: - virtual ~z3_exception() {} + virtual ~z3_exception() = default; virtual char const * msg() const = 0; virtual unsigned error_code() const; bool has_error_code() const;